diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9b03a24a3..3afb3ef9d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -171,28 +171,6 @@ jobs: rm -rf "${MTG_PKG}" "${MTG_PKG}.tar.gz" ;; esac - case "${{ matrix.platform }}" in - amd64) - curl -sfLRO $CURL_RETRY "https://github.com/EAimTY/tuic/releases/download/tuic-server-1.0.0/tuic-server-1.0.0-x86_64-unknown-linux-musl" - mv "tuic-server-1.0.0-x86_64-unknown-linux-musl" "tuic-server" - chmod +x "tuic-server" - ;; - arm64) - curl -sfLRO $CURL_RETRY "https://github.com/EAimTY/tuic/releases/download/tuic-server-1.0.0/tuic-server-1.0.0-aarch64-unknown-linux-musl" - mv "tuic-server-1.0.0-aarch64-unknown-linux-musl" "tuic-server" - chmod +x "tuic-server" - ;; - armv7) - curl -sfLRO $CURL_RETRY "https://github.com/EAimTY/tuic/releases/download/tuic-server-1.0.0/tuic-server-1.0.0-armv7-unknown-linux-musleabihf" - mv "tuic-server-1.0.0-armv7-unknown-linux-musleabihf" "tuic-server" - chmod +x "tuic-server" - ;; - 386) - curl -sfLRO $CURL_RETRY "https://github.com/EAimTY/tuic/releases/download/tuic-server-1.0.0/tuic-server-1.0.0-i686-unknown-linux-musl" - mv "tuic-server-1.0.0-i686-unknown-linux-musl" "tuic-server" - chmod +x "tuic-server" - ;; - esac cd ../.. - name: Package @@ -325,9 +303,6 @@ jobs: Move-Item "mtg-tmp/$MTG_PKG/mtg-multi.exe" "mtg-windows-amd64.exe" Remove-Item -Recurse -Force "mtg-tmp", "$MTG_PKG.zip" - # TUIC sidecar for Windows - curl.exe -sfLRo "tuic-server-windows-amd64.exe" --retry 5 --retry-all-errors --retry-delay 3 "https://github.com/EAimTY/tuic/releases/download/tuic-server-1.0.0/tuic-server-1.0.0-x86_64-pc-windows-msvc.exe" - cd .. Copy-Item -Path ..\windows_files\* -Destination . -Recurse cd .. diff --git a/DockerInit.sh b/DockerInit.sh index d00dc19e6..f488635a2 100755 --- a/DockerInit.sh +++ b/DockerInit.sh @@ -50,27 +50,6 @@ tar -xzf "${MTG_PKG}.tar.gz" mv "${MTG_PKG}/mtg-multi" "mtg-linux-${FNAME}" rm -rf "${MTG_PKG}" "${MTG_PKG}.tar.gz" chmod +x "mtg-linux-${FNAME}" -case $FNAME in - amd64) - curl -sfLRo "tuic-server" "https://github.com/EAimTY/tuic/releases/download/tuic-server-1.0.0/tuic-server-1.0.0-x86_64-unknown-linux-musl" - ;; - arm64) - curl -sfLRo "tuic-server" "https://github.com/EAimTY/tuic/releases/download/tuic-server-1.0.0/tuic-server-1.0.0-aarch64-unknown-linux-musl" - ;; - arm32) - curl -sfLRo "tuic-server" "https://github.com/EAimTY/tuic/releases/download/tuic-server-1.0.0/tuic-server-1.0.0-armv7-unknown-linux-musleabihf" - ;; - i386) - curl -sfLRo "tuic-server" "https://github.com/EAimTY/tuic/releases/download/tuic-server-1.0.0/tuic-server-1.0.0-i686-unknown-linux-musl" - ;; -esac -if [ -f "tuic-server" ]; then - if [ ! -s "tuic-server" ]; then - echo "DockerInit: tuic-server download was empty" >&2 - exit 1 - fi - chmod +x "tuic-server" -fi curl -sfLRO https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat curl -sfLRO https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat curl -sfLRo geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat diff --git a/docs/architecture.md b/docs/architecture.md index 0aa5c1150..47d4b0259 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -19,8 +19,8 @@ Xray JSON config from that state, supervises the Xray child process, and exposes WebSocket API. A React SPA (built by Vite, embedded into the Go binary) is the UI. A second, separate HTTP server serves **subscription links** to end users. -The panel supervises **managed child processes**: Xray-core itself and — when MTProto or -TUIC inbounds exist — dedicated child proxy binaries: +The panel supervises **managed child processes**: Xray-core itself and — when MTProto +inbounds exist — a dedicated child proxy binary: - **`mtg-multi` for MTProto inbounds** (`github.com/mhsanaei/mtg-multi`, a multi-secret fork built from source; `internal/mtproto/`): One process per inbound serves every attached @@ -28,10 +28,10 @@ TUIC inbounds exist — dedicated child proxy binaries: sponsored-channel ad-tags via `[secret-ad-tags]`. A client or ad-tag edit is hot-applied via the fork's management API (`PUT /secrets`, guarded by a per-process bearer token), with a process restart as the fallback on older binaries. -- **`tuic-server` for TUIC v5 inbounds** (`internal/tuic/`): One process per inbound runs on - loopback behind an in-process native Go UDP relay that owns the public port and meters - traffic deltas. The sidecar handles decrypted client traffic standalone, independent of - Xray routing and outbounds. + +In contrast, **AmneziaWG** (`internal/amneziawgnet/`) and **TUIC v5** (`internal/tuic/`) run as +**in-process native Go servers** without external child processes, bridging client traffic into +Xray-core via loopback SOCKS5 relays. Servers and processes, all launched from `main.go`: @@ -41,7 +41,6 @@ Servers and processes, all launched from `main.go`: | **Subscription** | `internal/sub` | Public endpoint that hands out client configs (raw / JSON / Clash) | `subPort` setting | | **Xray-core** | supervised via `internal/xray` | The actual proxy engine; a child process, not Go code | `inbounds[].port` | | **mtg-multi** | supervised via `internal/mtproto` | MTProto proxy child process for MTProto inbounds (multi-secret) | per inbound | -| **tuic-server** | supervised via `internal/tuic` | TUIC v5 proxy child process fronted by a Go UDP relay | per inbound | Two key ideas that explain most of the complexity: diff --git a/docs/content/docs/en/config/clients.mdx b/docs/content/docs/en/config/clients.mdx index 8f3f0feeb..05c7df5df 100644 --- a/docs/content/docs/en/config/clients.mdx +++ b/docs/content/docs/en/config/clients.mdx @@ -18,7 +18,7 @@ inbounds** at once, with per-client traffic accounting. | **Auth** | Hysteria2 | The client credential. | | **Flow** | VLESS | XTLS flow, e.g. `xtls-rprx-vision`. | | **Limit IP** | all (except TUIC) | Max simultaneous source IPs (enforced via Fail2ban). | -| **Total (GB)** | all (except TUIC) | Traffic quota; the client is disabled when exhausted (for TUIC, limits are set at the inbound level). | +| **Total (GB)** | all | Traffic quota; the client is disabled when exhausted. | | **Expiry** | all | Date after which the client stops working. | | **Auto renewal** | all | Disabled, fixed interval in days, calendar weekly, or calendar monthly. | | **Telegram ID**| all | Links the client to a Telegram user for self-service/notifications.| diff --git a/docs/content/docs/en/config/inbounds.mdx b/docs/content/docs/en/config/inbounds.mdx index b409f8180..30eeafb1f 100644 --- a/docs/content/docs/en/config/inbounds.mdx +++ b/docs/content/docs/en/config/inbounds.mdx @@ -64,7 +64,7 @@ The inbound editor accepts these protocols: | **Mixed (SOCKS/HTTP)** | A combined SOCKS + HTTP listener. | | **Dokodemo-door / Tunnel** | Port forwarding / traffic redirect. | | **MTProto** | Telegram MTProto proxy, served by a bundled `mtg` process (not Xray). | -| **TUIC** | QUIC-based proxy protocol (v5), served by a bundled `tuic-server` process. See [TUIC](/docs/config/tuic). | +| **TUIC** | QUIC-based proxy protocol (v5), served by an in-process native Go server. See [TUIC](/docs/config/tuic). | Hysteria2 isn't a separate protocol internally — it's the `hysteria` protocol diff --git a/docs/content/docs/en/config/tuic.mdx b/docs/content/docs/en/config/tuic.mdx index 1d972cac8..7232a9f65 100644 --- a/docs/content/docs/en/config/tuic.mdx +++ b/docs/content/docs/en/config/tuic.mdx @@ -10,10 +10,7 @@ and custom congestion control algorithms to maintain stable connections over los unstable networks. - Like MTProto, TUIC runs as a **managed sidecar process** (`tuic-server` 1.0.0, - written in Rust) rather than inside Xray-core. The panel manages the binary - lifecycle, generates configurations, monitors process health, and tracks - inbound traffic and client online presence. + TUIC runs as an **in-process native Go server** inside 3x-ui. Decrypted traffic is bridged into Xray-core via a loopback SOCKS5 tunnel, enabling full Xray routing rules, cascading outbounds (e.g. TUIC → VLESS / WARP), per-client traffic quotas (`totalGB`), and zero-downtime hot user updates without restarting the port. ## Key settings @@ -25,7 +22,7 @@ unstable networks. | **Port** | UDP port for incoming client QUIC connections. | | **Certificate & Key** | Full TLS certificate chain and private key. QUIC mandates TLS encryption; self-signed certificates or valid Let's Encrypt / ACME certs are supported. | | **SNI** | Server Name Indication matching your TLS certificate domain name. | -| **Congestion Control** | QUIC congestion control algorithm: `bbr` (recommended for high throughput), `cubic`, or `new_reno`. | +| **Congestion Control** | QUIC congestion control algorithm: `bbr` (recommended for high throughput), `cubic`, or `new_reno`. The server runs `bbr` or `new_reno`; `cubic` is sent to clients but served as `new_reno`. | | **ALPN** | Application-Layer Protocol Negotiation tokens (default: `h3`). | | **UDP Relay Mode** | Packet encapsulation mode: `native` (QUIC datagrams, recommended) or `quic`. | | **Zero-RTT Handshake** | Enables 0-RTT connection resumption to eliminate initial handshake round-trips for returning clients. | @@ -102,12 +99,12 @@ TUIC share links use standard URI formatting: tuic://:@:?congestion_control=bbr&alpn=h3&sni=vpn.example.com&udp_relay_mode=native&allow_insecure=0#Remark ``` -## Architecture & Notes +## Architecture & Features - - **Standalone sidecar**: The panel ships pre-compiled `tuic-server` musl binaries on Linux (amd64, arm64, armv7, 386) and executable for Windows. - - **Traffic accounting & limits**: The panel owns the inbound's public UDP port with a small relay and runs `tuic-server` behind it on a loopback port, so the inbound's upload and download bytes are counted exactly on every OS and enforced at the **inbound level** (`inbounds.total`); `tuic-server` therefore logs `127.0.0.1` as every client's address. Because upstream `tuic-server` does not provide an internal per-user metrics API, individual client traffic limits (`totalGB`) are not supported for TUIC clients. Client access can be controlled via expiration timestamps (`expiryTime`) and manual enable/disable toggles. - - **Online status & "start after first use"**: The panel detects a client's activity from the sidecar's Info log lines (they carry the client UUID), so those features need the inbound's log level at `info` or `debug`; `warn` and `error` silence them. - - **Client updates & connections**: Because upstream `tuic-server` lacks dynamic user reload APIs, client modifications (adding, updating, or disabling clients) restart the sidecar process and momentarily reset active connections. - - **Deployment**: A TUIC inbound can be created on, or cloned to, a sub-node. The node's own panel runs the sidecar, so the node must run panel v3.8.0 or newer; the master refuses an older node. + - **Native in-process Go engine**: TUIC v5 runs 100% natively in Go within the 3x-ui process. No external binaries or sidecars to download or maintain. + - **Full Xray routing & cascading**: Decrypted traffic passes directly through Xray's routing engine. Inbound tags (`in--udp`) work seamlessly with routing rules, domain/IP blocks, and cascading to any outbound proxy (VLESS, Shadowsocks, WARP, etc.). + - **Per-client traffic limits & expiration**: Individual traffic quotas (`totalGB`) and expiration timestamps (`expiryTime`) are tracked and enforced for each client. + - **Zero-downtime client updates**: Adding, modifying, or disabling clients updates the in-memory user registry instantly without restarting the UDP port or interrupting existing client sessions. + - **Deployment**: A TUIC inbound can be created on, or cloned to, a sub-node. The node's own panel runs the TUIC server, so the node must run panel v3.8.0 or newer; the master refuses an older node. diff --git a/docs/content/docs/fa/config/clients.mdx b/docs/content/docs/fa/config/clients.mdx index e1ba5af62..8090c6ab2 100644 --- a/docs/content/docs/fa/config/clients.mdx +++ b/docs/content/docs/fa/config/clients.mdx @@ -18,7 +18,7 @@ icon: Users | **Auth** | Hysteria2 | اعتبارنامه‌ی کلاینت. | | **Flow** | VLESS | جریان XTLS، برای مثال `xtls-rprx-vision`. | | **Limit IP** | همه (به‌جز TUIC) | بیشینه‌ی تعداد IPهای مبدأ هم‌زمان (با Fail2ban اعمال می‌شود). | -| **Total (GB)** | همه (به‌جز TUIC) | سهمیه‌ی ترافیک؛ هنگام اتمام، کلاینت غیرفعال می‌شود (برای TUIC محدودیت در سطح ورودی تعیین می‌شود). | +| **Total (GB)** | همه | سهمیه‌ی ترافیک؛ هنگام اتمام، کلاینت غیرفعال می‌شود. | | **Expiry** | همه | تاریخی که پس از آن کلاینت از کار می‌افتد. | | **Reset** | همه | دوره‌ی تمدید خودکار به **روز** (سهمیه را از نو می‌چرخاند). | | **Telegram ID**| همه | کلاینت را به یک کاربر Telegram برای سلف‌سرویس/اعلان‌ها پیوند می‌دهد.| diff --git a/docs/content/docs/fa/config/inbounds.mdx b/docs/content/docs/fa/config/inbounds.mdx index a293296e2..1c1d2f710 100644 --- a/docs/content/docs/fa/config/inbounds.mdx +++ b/docs/content/docs/fa/config/inbounds.mdx @@ -64,7 +64,7 @@ TLS یا REALITY) را انتخاب کنید. به [انتقال‌ها](/docs/c | **Mixed (SOCKS/HTTP)** | یک شنونده ترکیبی SOCKS + HTTP. | | **Dokodemo-door / Tunnel** | فورواردینگ پورت / هدایت ترافیک. | | **MTProto** | پراکسی MTProto تلگرام که توسط یک فرایند همراه `mtg` سرویس می‌شود (نه Xray). | -| **TUIC** | پروتکل پراکسی مبتنی بر QUIC نسخه ۵ که توسط فرایند `tuic-server` ارائه می‌شود. مشاهده [TUIC](/docs/config/tuic). | +| **TUIC** | پروتکل پراکسی مبتنی بر QUIC نسخه ۵ که به صورت سرور بومی Go درون فرایند ارائه می‌شود. مشاهده [TUIC](/docs/config/tuic). | Hysteria2 در سطح داخلی یک پروتکل جداگانه نیست — همان پروتکل `hysteria` است که diff --git a/docs/content/docs/ru/config/clients.mdx b/docs/content/docs/ru/config/clients.mdx index 15686f80e..e5338d838 100644 --- a/docs/content/docs/ru/config/clients.mdx +++ b/docs/content/docs/ru/config/clients.mdx @@ -19,7 +19,7 @@ icon: Users | **Auth** | Hysteria2 | Учётные данные клиента. | | **Flow** | VLESS | Поток XTLS, например `xtls-rprx-vision`. | | **Limit IP** | все (кроме TUIC) | Максимум одновременных IP-адресов источника (контролируется через Fail2ban). | -| **Total (GB)** | все (кроме TUIC) | Квота трафика; при исчерпании клиент отключается (для TUIC лимит задаётся на уровне инбаунда). | +| **Total (GB)** | все | Квота трафика; при исчерпании клиент отключается. | | **Expiry** | все | Дата, после которой клиент перестаёт работать. | | **Reset** | все | Период автопродления в **днях** (обнуляет квоту). | | **Telegram ID**| все | Привязывает клиента к пользователю Telegram для самообслуживания/уведомлений.| diff --git a/docs/content/docs/ru/config/inbounds.mdx b/docs/content/docs/ru/config/inbounds.mdx index 7bdb3ca8d..763c76ba5 100644 --- a/docs/content/docs/ru/config/inbounds.mdx +++ b/docs/content/docs/ru/config/inbounds.mdx @@ -65,7 +65,7 @@ icon: ArrowDownToLine | **Mixed (SOCKS/HTTP)** | Совмещённый слушатель SOCKS + HTTP. | | **Dokodemo-door / Tunnel** | Перенаправление портов / перенаправление трафика. | | **MTProto** | Прокси Telegram MTProto, обслуживаемый встроенным процессом `mtg` (не Xray). | -| **TUIC** | Протокол проксирования на базе QUIC (v5), обслуживаемый встроенным процессом `tuic-server`. См. [TUIC](/docs/config/tuic). | +| **TUIC** | Протокол проксирования на базе QUIC (v5), обслуживаемый встроенным сервером на Go. См. [TUIC](/docs/config/tuic). | Hysteria2 внутренне не является отдельным протоколом — это протокол `hysteria` diff --git a/docs/content/docs/ru/config/tuic.mdx b/docs/content/docs/ru/config/tuic.mdx index 3de967fa3..ab01e5ccf 100644 --- a/docs/content/docs/ru/config/tuic.mdx +++ b/docs/content/docs/ru/config/tuic.mdx @@ -9,10 +9,7 @@ icon: Zap и настраиваемый контроль перегрузок для поддержания стабильной связи на сетях с потерями пакетов. - Как и MTProto, TUIC работает как **изолированный процесс-сайдкар** (`tuic-server` 1.0.0, - написан на Rust), а не внутри Xray-core. Панель управляет жизненным циклом бинарника, - генерирует конфигурации, отслеживает его состояние, фиксирует общий трафик инбаунда - и онлайн-активность клиентов. + TUIC работает как **встроенный нативный Go-сервер** прямо внутри процесса 3x-ui. Расшифрованный трафик направляется в ядро Xray-core через локальный SOCKS5-мост, что обеспечивает полную поддержку правил маршрутизации Xray, каскадирования (например, TUIC → VLESS / WARP), персональных квот клиентов (`totalGB`) и горячего обновления пользователей без обрыва соединений. ## Ключевые параметры @@ -24,7 +21,7 @@ icon: Zap | **Порт** | UDP-порт для входящих QUIC-соединений клиентов. | | **Сертификат и ключ** | Полная цепочка SSL-сертификата и приватный ключ. Протокол QUIC требует обязательного шифрования TLS; поддерживаются сертификаты Let's Encrypt / ACME или самоподписанные. | | **SNI** | Имя сервера (Server Name Indication), совпадающее с доменным именем в сертификате. | -| **Контроль перегрузок** | Алгоритм контроля перегрузок QUIC: `bbr` (рекомендуется для максимальной скорости), `cubic` или `new_reno`. | +| **Контроль перегрузок** | Алгоритм контроля перегрузок QUIC: `bbr` (рекомендуется для максимальной скорости), `cubic` или `new_reno`. Сервер работает с `bbr` или `new_reno`; `cubic` передаётся клиентам, но на сервере применяется как `new_reno`. | | **ALPN** | Токены протоколов уровня приложений (по умолчанию: `h3`). | | **Режим UDP Relay** | Режим инкапсуляции пакетов: `native` (QUIC datagrams, рекомендуется) или `quic`. | | **Zero-RTT Handshake** | Включает 0-RTT возобновление сессий для мгновенного повторного подключения клиентов без ожидания завершения рукопожатия. | @@ -101,12 +98,12 @@ proxies: tuic://:@:?congestion_control=bbr&alpn=h3&sni=vpn.example.com&udp_relay_mode=native&allow_insecure=0#Remark ``` -## Архитектура и примечания +## Архитектура и возможности - - **Автономный сайдкар**: Панель поставляется со скомпилированными статическими `musl`-бинарниками `tuic-server` для Linux (amd64, arm64, armv7, 386) и исполняемым файлом для Windows. - - **Учёт трафика и лимиты**: Панель сама занимает публичный UDP-порт инбаунда небольшим relay и запускает `tuic-server` за ним на loopback-порту, поэтому входящие и исходящие байты инбаунда считаются точно на любой ОС и ограничиваются на **уровне инбаунда** (`inbounds.total`); в логах `tuic-server` адресом каждого клиента будет `127.0.0.1`. Поскольку апстрим `tuic-server` не предоставляет внутреннего API метрик по отдельным пользователям, персональные квоты трафика (`totalGB`) для клиентов TUIC не поддерживаются. Доступ клиентов контролируется по сроку действия (`expiryTime`) и переключателю активности. - - **Статус онлайн и «старт после первого использования»**: Панель определяет активность клиента по строкам Info в логе сайдкара (в них есть UUID клиента), поэтому этим функциям нужен уровень логов `info` или `debug`; `warn` и `error` их отключают. - - **Изменения клиентов и соединения**: Поскольку апстрим `tuic-server` не поддерживает динамическую перезагрузку пользователей без перезапуска, любое изменение списка клиентов (добавление, редактирование или отключение) перезапускает процесс сайдкара и кратковременно сбрасывает активные соединения. - - **Развёртывание**: Инбаунд TUIC можно создать на дочернем узле или клонировать туда. Sidecar запускает панель самого узла, поэтому на узле нужна панель v3.8.0 или новее; более старый узел главная панель отклоняет. + - **Нативный Go-движок**: TUIC v5 работает на 100% нативно на Go внутри процесса 3x-ui. Никаких внешних сторонних бинарников скачивать не требуется. + - **Маршрутизация и каскады в Xray**: Трафик проходит через движок маршрутизации Xray. Теги инбаундов (`in--udp`) полноценно участвуют в правилах маршрутизации (Routing Rules), блокировках geosite/geoip и перенаправлении в любые аутбаунды (VLESS, Shadowsocks, WARP и др.). + - **Персональные квоты трафика**: Лимиты трафика (`totalGB`) и сроки действия (`expiryTime`) учитываются и применяются индивидуально для каждого клиента. + - **Горячее обновление без обрыва связи**: Добавление, редактирование или отключение клиентов обновляет реестр пользователей в памяти без перезапуска порта и без сброса активных сессий других пользователей. + - **Развёртывание**: Инбаунд TUIC можно создать на дочернем узле или клонировать туда. TUIC-сервер запускает панель самого узла, поэтому на узле нужна панель v3.8.0 или новее; более старый узел главная панель отклоняет. diff --git a/docs/content/docs/zh/config/clients.mdx b/docs/content/docs/zh/config/clients.mdx index afc8d0812..e395adedc 100644 --- a/docs/content/docs/zh/config/clients.mdx +++ b/docs/content/docs/zh/config/clients.mdx @@ -17,7 +17,7 @@ icon: Users | **Auth** | Hysteria2 | 客户端凭据。 | | **Flow** | VLESS | XTLS 流控,例如 `xtls-rprx-vision`。 | | **Limit IP** | 全部(TUIC 除外) | 最大同时连接的源 IP 数量(通过 Fail2ban 强制执行)。 | -| **Total (GB)** | 全部(TUIC 除外) | 流量配额;用尽后客户端将被禁用(对于 TUIC,限制在入站级别设置)。 | +| **Total (GB)** | 全部 | 流量配额;用尽后客户端将被禁用。 | | **Expiry** | 全部 | 该日期之后客户端停止工作。 | | **自动续期** | 全部 | 关闭、固定天数、日历每周或日历每月。 | | **Telegram ID**| 全部 | 将客户端关联到 Telegram 用户,用于自助服务/通知。 | diff --git a/docs/content/docs/zh/config/inbounds.mdx b/docs/content/docs/zh/config/inbounds.mdx index 46199c821..5c05fdd30 100644 --- a/docs/content/docs/zh/config/inbounds.mdx +++ b/docs/content/docs/zh/config/inbounds.mdx @@ -61,7 +61,7 @@ icon: ArrowDownToLine | **Mixed (SOCKS/HTTP)** | SOCKS + HTTP 的组合监听器。 | | **Dokodemo-door / Tunnel** | 端口转发 / 流量重定向。 | | **MTProto** | Telegram MTProto 代理,由内置的 `mtg` 进程提供(而非 Xray)。 | -| **TUIC** | 基于 QUIC 的代理协议(v5),由内置的 `tuic-server` 进程提供。参见 [TUIC](/docs/config/tuic)。 | +| **TUIC** | 基于 QUIC 的代理协议(v5),由进程内原生 Go 服务器提供。参见 [TUIC](/docs/config/tuic)。 | 在内部,Hysteria2 并不是一个独立的协议——它是把传输版本设为 2 的 `hysteria` diff --git a/frontend/src/lib/tuic.ts b/frontend/src/lib/tuic.ts new file mode 100644 index 000000000..e2a1e122b --- /dev/null +++ b/frontend/src/lib/tuic.ts @@ -0,0 +1,35 @@ +export type TuicCongestionController = 'bbr' | 'cubic' | 'new_reno'; + +export function normalizeTuicCongestionController(value: unknown): TuicCongestionController { + if (typeof value !== 'string' || value.trim() === '') return 'bbr'; + + switch (value.trim().toLowerCase()) { + case 'bbr': + return 'bbr'; + case 'cubic': + return 'cubic'; + case 'reno': + case 'new_reno': + return 'new_reno'; + default: + return 'new_reno'; + } +} + +export function resolveTuicServerSettings( + settings: Record, +): Record { + const nested = + settings.server && typeof settings.server === 'object' && !Array.isArray(settings.server) + ? (settings.server as Record) + : {}; + const result: Record = { ...settings }; + delete result.server; + delete result.clients; + for (const [key, value] of Object.entries(nested)) { + if (value == null || value === '' || (Array.isArray(value) && value.length === 0)) continue; + if (typeof value === 'number' && value <= 0) continue; + result[key] = value; + } + return result; +} diff --git a/frontend/src/lib/xray/inbound-form-adapter.ts b/frontend/src/lib/xray/inbound-form-adapter.ts index c6d2196f5..3f181b47a 100644 --- a/frontend/src/lib/xray/inbound-form-adapter.ts +++ b/frontend/src/lib/xray/inbound-form-adapter.ts @@ -1,3 +1,4 @@ +import { resolveTuicServerSettings } from '@/lib/tuic'; import type { InboundFormValues, ShareAddrStrategy, @@ -168,7 +169,12 @@ function stripTlsCertUseFile(stream: Record): void { export function rawInboundToFormValues(row: RawInboundRow): InboundFormValues { const protocol = (row.protocol || 'vless') as InboundSettings['protocol']; - const settings = coerceJsonObject(row.settings) as InboundSettings['settings']; + const rawSettings = coerceJsonObject(row.settings); + const settings = ( + protocol === 'tuic' + ? { clients: rawSettings.clients, server: resolveTuicServerSettings(rawSettings) } + : rawSettings + ) as InboundSettings['settings']; const rawStream = coerceJsonObject(row.streamSettings); const streamSettings = Object.keys(rawStream).length > 0 ? (rawStream as StreamSettings) : undefined; diff --git a/frontend/src/lib/xray/inbound-link.ts b/frontend/src/lib/xray/inbound-link.ts index 5bed00b15..c604922a1 100644 --- a/frontend/src/lib/xray/inbound-link.ts +++ b/frontend/src/lib/xray/inbound-link.ts @@ -17,6 +17,7 @@ import { parseGeckoPacketSize } from '@/lib/xray/forms/transport/FinalMaskForm'; import { getHeaderValue } from './headers'; import { canEnableTlsFlow } from './protocol-capabilities'; import { deriveSpiderX } from './spider-x'; +import { normalizeTuicCongestionController, resolveTuicServerSettings } from '@/lib/tuic'; // Share-link generators. Each per-protocol fn takes a typed inbound plus // client overrides and returns a URL (or '' when the protocol doesn't @@ -914,15 +915,16 @@ export function genTuicLink(input: GenTuicLinkInput): string { if (!clientUuid || !clientPassword) return ''; const rawSettings = inbound.settings as Record; - const server = (rawSettings.server as Record) ?? rawSettings; + const server = resolveTuicServerSettings(rawSettings); const host = formatUrlHost(externalProxy?.dest || address); const targetPort = externalProxy?.port || port; const url = new URL( `tuic://${encodeURIComponent(clientUuid)}:${encodeURIComponent(clientPassword)}@${host}:${targetPort}`, ); - const cc = - (server.congestion_control as string) || (rawSettings.congestion_control as string) || 'bbr'; + const cc = normalizeTuicCongestionController( + server.congestion_control ?? rawSettings.congestion_control, + ); url.searchParams.set('congestion_control', cc); const epAlpn = externalProxyAlpn(externalProxy?.alpn); diff --git a/frontend/src/pages/clients/ClientBulkAddModal.tsx b/frontend/src/pages/clients/ClientBulkAddModal.tsx index 2c1547b3c..2c692a6c2 100644 --- a/frontend/src/pages/clients/ClientBulkAddModal.tsx +++ b/frontend/src/pages/clients/ClientBulkAddModal.tsx @@ -130,19 +130,6 @@ export default function ClientBulkAddModal({ return ''; }, [inboundIds, inbounds]); - const tuicIds = useMemo(() => { - const ids = new Set(); - for (const row of inbounds || []) { - if (row && row.protocol === 'tuic') ids.add(row.id); - } - return ids; - }, [inbounds]); - - const hasTuic = useMemo( - () => (inboundIds || []).some((id) => tuicIds.has(id)), - [inboundIds, tuicIds], - ); - useEffect(() => { if (!showFlow && flow) { methods.setValue('flow', ''); @@ -405,9 +392,7 @@ export default function ClientBulkAddModal({ Number(v) || 0 }} > diff --git a/frontend/src/pages/clients/ClientFormModal.tsx b/frontend/src/pages/clients/ClientFormModal.tsx index 213afd019..d2ea2a9fe 100644 --- a/frontend/src/pages/clients/ClientFormModal.tsx +++ b/frontend/src/pages/clients/ClientFormModal.tsx @@ -452,19 +452,6 @@ export default function ClientFormModal({ return ids; }, [inbounds]); - const tuicIds = useMemo(() => { - const ids = new Set(); - for (const row of inbounds || []) { - if (row && row.protocol === 'tuic') ids.add(row.id); - } - return ids; - }, [inbounds]); - - const hasTuic = useMemo( - () => (inboundIds || []).some((id) => tuicIds.has(id)), - [inboundIds, tuicIds], - ); - const mtprotoDomain = useMemo(() => { for (const id of inboundIds || []) { const ib = (inbounds || []).find((row) => row.id === id); @@ -884,11 +871,7 @@ export default function ClientFormModal({ Number(v) || 0 }} > diff --git a/frontend/src/pages/clients/tuicConfig.ts b/frontend/src/pages/clients/tuicConfig.ts index 1eea37dca..b2e90afe1 100644 --- a/frontend/src/pages/clients/tuicConfig.ts +++ b/frontend/src/pages/clients/tuicConfig.ts @@ -1,6 +1,7 @@ import type { HostEndpoint } from '@/lib/hosts/host-link'; import { formatInboundLabel } from '@/lib/inbounds/label'; import { preferPublicHost, resolveShareHost } from '@/lib/xray/inbound-link'; +import { normalizeTuicCongestionController } from '@/lib/tuic'; import type { ClientRecord, InboundOption } from '@/hooks/useClients'; export function isTuicClient(client: ClientRecord | null | undefined): boolean { @@ -39,7 +40,7 @@ export function buildTuicClientConfig( ? tuicServer.alpn : ['h3', 'spdy/3.1']; const sni = hostEndpoint?.sni || tuicServer?.sni || endpointHost; - const cc = tuicServer?.congestion_control || 'bbr'; + const cc = normalizeTuicCongestionController(tuicServer?.congestion_control); const udpRelay = tuicServer?.udp_relay_mode || 'native'; const reduceRtt = tuicServer?.zero_rtt_handshake ?? true; diff --git a/frontend/src/pages/inbounds/form/protocols/tuic.tsx b/frontend/src/pages/inbounds/form/protocols/tuic.tsx index 6f1503679..091528a4c 100644 --- a/frontend/src/pages/inbounds/form/protocols/tuic.tsx +++ b/frontend/src/pages/inbounds/form/protocols/tuic.tsx @@ -4,6 +4,7 @@ import { AutoComplete, Button, Collapse, + Divider, Form, Input, InputNumber, @@ -20,7 +21,7 @@ import { HttpUtil } from '@/utils'; export default function TuicFields() { const { t } = useTranslation(); - const { control, setValue, getValues } = useFormContext(); + const { control, setValue } = useFormContext(); const [loadingPanelCert, setLoadingPanelCert] = useState(false); const sni = (useWatch({ control, name: 'settings.server.sni' }) ?? '') as string; @@ -30,24 +31,12 @@ export default function TuicFields() { const handleSniChange = (newSni: string) => { setValue('settings.server.sni', newSni); - const cleanSni = newSni.trim(); - if (!cleanSni) return; - - const currentCert = String(getValues('settings.server.certificate') || ''); - const currentKey = String(getValues('settings.server.private_key') || ''); - - if (!currentCert || currentCert.startsWith('/root/cert/')) { - setValue('settings.server.certificate', `/root/cert/${cleanSni}/fullchain.pem`); - } - if (!currentKey || currentKey.startsWith('/root/cert/')) { - setValue('settings.server.private_key', `/root/cert/${cleanSni}/privkey.pem`); - } }; const autofillFromSni = () => { const cleanSni = (sni || '').trim(); if (!cleanSni) { - message.warning(t('pages.xray.tuic.sniHint')); + message.warning(t('pages.xray.tuic.sniRequired')); return; } setValue('settings.server.certificate', `/root/cert/${cleanSni}/fullchain.pem`); @@ -147,7 +136,7 @@ export default function TuicFields() { name={['settings', 'server', 'max_udp_relay_packet_size']} label={t('pages.xray.tuic.maxUdpRelayPacketSize')} > - + ), @@ -156,20 +145,6 @@ export default function TuicFields() { return ( <> - - - handleSniChange(e.target.value)} - style={{ flex: 1 }} - /> - - - - {t('pages.inbounds.setDefaultCert')} +