mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-14 02:12:11 +03:00
fix(sub): enable ML-KEM for Mihomo REALITY subscriptions (#6451)
This commit is contained in:
@@ -118,13 +118,21 @@ vless://<uuid>@<server>:443?security=reality&pbk=<public-key>&sid=<short-id>&sni
|
||||
- **Leaked private key.** Only ever distribute the **public** key to clients.
|
||||
- **Wrong flow.** REALITY + XTLS-Vision needs `flow = xtls-rprx-vision` on both
|
||||
the inbound client entry and the share link.
|
||||
- **Old client cores rejected by default.** An empty **Min Client Ver** is not
|
||||
"no limit": Xray-core falls back to the built-in minimum of the core build you
|
||||
run (26.3.27 in current releases) that keeps client TLS fingerprints fresh, so
|
||||
third-party cores such as Mihomo and sing-box fail REALITY verification even
|
||||
with a correct config — clients see timeouts while only Xray-core based apps
|
||||
connect. Set it to `1.0.0` only if you must support them; that also re-admits
|
||||
outdated fingerprints.
|
||||
- **Client version limits.** Xray-core v26.9.8+ no longer sets a minimum when
|
||||
**Min Client Ver** is empty. An explicitly saved minimum still applies. Earlier
|
||||
builds may use a built-in minimum (such as `26.3.27`), rejecting third-party
|
||||
clients even with correct keys. Check the running core version before changing
|
||||
this gate; lowering it also admits older fingerprints.
|
||||
- **Mihomo and ML-KEM.** Xray-core v26.9.8+ independently requires an
|
||||
`X25519MLKEM768` key share before the optional `X25519` share. The Clash/Mihomo
|
||||
YAML subscription enables `reality-opts.support-x25519mlkem768` for REALITY
|
||||
nodes, including external links, and uses `chrome` when no fingerprint was set.
|
||||
Explicit fingerprints are preserved: choose one that offers ML-KEM (`chrome`
|
||||
with Mihomo's uTLS v1.8.7); enabling the flag cannot upgrade an old fingerprint.
|
||||
Raw `vless://` links do not carry this Mihomo option, so clients importing them
|
||||
directly still need a persistent override. Very old REALITY servers that reject
|
||||
ML-KEM require a per-node client override setting this option to `false`, or a
|
||||
server upgrade. Clearing the version limit alone does not fix the handshake.
|
||||
|
||||
</Callout>
|
||||
|
||||
|
||||
@@ -118,13 +118,29 @@ vless://<uuid>@<server>:443?security=reality&pbk=<public-key>&sid=<short-id>&sni
|
||||
- **نشت کلید خصوصی.** فقط و فقط **کلید عمومی** را میان کلاینتها توزیع کنید.
|
||||
- **جریان نادرست.** REALITY + XTLS-Vision به `flow = xtls-rprx-vision` هم در ورودیِ
|
||||
مدخل کلاینت و هم در لینک اشتراکگذاری نیاز دارد.
|
||||
- **هستههای قدیمی کلاینت بهطور پیشفرض رد میشوند.** خالی گذاشتن
|
||||
**حداقل نسخه کلاینت** به معنای «بدون محدودیت» نیست: Xray-core به حداقل داخلیِ
|
||||
نسخهٔ هستهای که اجرا میکنید (در نسخههای فعلی 26.3.27) بازمیگردد تا اثر انگشتهای TLS کلاینتها تازه
|
||||
بمانند؛ در نتیجه هستههای شخص ثالث مانند Mihomo و sing-box حتی با پیکربندی
|
||||
کاملاً درست در تأیید REALITY شکست میخورند — کلاینتها تایماوت میبینند و فقط
|
||||
اپلیکیشنهای مبتنی بر Xray-core وصل میشوند. تنها در صورت نیاز به پشتیبانی از
|
||||
آنها مقدار `1.0.0` را تنظیم کنید؛ این کار اثر انگشتهای قدیمی را هم میپذیرد.
|
||||
- **محدودیت نسخهٔ کلاینت.** از نسخهٔ
|
||||
`Xray-core v26.9.8`
|
||||
به بعد، خالی بودن **حداقل نسخه کلاینت** حد پایین پیشفرض ایجاد نمیکند؛ مقدار ذخیرهشده همچنان اعمال میشود.
|
||||
نسخههای قدیمیتر ممکن است حد داخلی مانند
|
||||
`26.3.27`
|
||||
داشته باشند. ابتدا نسخهٔ هستهٔ در حال اجرا را بررسی کنید؛ کاهش حد، اثر انگشتهای قدیمی را نیز مجاز میکند.
|
||||
- **Mihomo و ML-KEM.** هستهٔ جدید مستقل از محدودیت نسخه، وجود
|
||||
`X25519MLKEM768`
|
||||
را پیش از کلید اختیاری
|
||||
`X25519`
|
||||
لازم میداند. اشتراک YAML برای REALITY، از جمله لینکهای خارجی، گزینهٔ
|
||||
`reality-opts.support-x25519mlkem768`
|
||||
را فعال میکند و در نبود اثر انگشت از
|
||||
`chrome`
|
||||
استفاده میکند. انتخاب صریح حفظ میشود و باید از ML-KEM پشتیبانی کند؛ برای
|
||||
`uTLS v1.8.7`
|
||||
در Mihomo از Chrome استفاده کنید. فعال کردن گزینه، اثر انگشت قدیمی را ارتقا نمیدهد.
|
||||
لینک خام
|
||||
`vless://`
|
||||
این گزینه را منتقل نمیکند و هنگام ورود مستقیم، بازنویسی پایدار در کلاینت لازم است.
|
||||
برای سرورهای بسیار قدیمی که ML-KEM را رد میکنند، گزینه را برای همان گره در کلاینت روی
|
||||
`false`
|
||||
بگذارید یا سرور را ارتقا دهید. حذف محدودیت نسخه بهتنهایی دستدهی را اصلاح نمیکند.
|
||||
|
||||
</Callout>
|
||||
|
||||
|
||||
@@ -123,14 +123,21 @@ vless://<uuid>@<server>:443?security=reality&pbk=<public-key>&sid=<short-id>&sni
|
||||
ключ.
|
||||
- **Неправильный поток.** Для REALITY + XTLS-Vision нужен `flow = xtls-rprx-vision`
|
||||
как в записи клиента входящего подключения, так и в ссылке для подключения.
|
||||
- **Старые ядра клиентов отклоняются по умолчанию.** Пустое поле
|
||||
**Мин. версия клиента** не означает «без ограничений»: Xray-core использует
|
||||
встроенный минимум используемой сборки ядра (26.3.27 в текущих релизах),
|
||||
который поддерживает свежесть
|
||||
TLS-отпечатков клиентов, поэтому сторонние ядра, такие как Mihomo и sing-box,
|
||||
не проходят проверку REALITY даже при корректной конфигурации — клиенты видят
|
||||
таймауты, а подключаются только приложения на базе Xray-core. Ставьте `1.0.0`,
|
||||
только если они вам необходимы; это также допустит устаревшие отпечатки.
|
||||
- **Ограничения версии клиента.** В Xray-core v26.9.8+ пустое поле
|
||||
**Мин. версия клиента** не задаёт нижнюю границу. Явно сохранённое ограничение
|
||||
продолжает действовать. Более ранние сборки могут использовать встроенный
|
||||
минимум (например, `26.3.27`) и отклонять сторонние клиенты с правильными ключами.
|
||||
Проверьте версию работающего ядра: снижение ограничения допускает старые отпечатки.
|
||||
- **Mihomo и ML-KEM.** Xray-core v26.9.8+ отдельно требует ключ
|
||||
`X25519MLKEM768` перед необязательным `X25519`. YAML-подписка Clash/Mihomo
|
||||
включает `reality-opts.support-x25519mlkem768` для REALITY, в том числе внешних
|
||||
ссылок, и выбирает `chrome`, если отпечаток не задан. Явный выбор сохраняется:
|
||||
нужен отпечаток с ML-KEM (`chrome` при uTLS v1.8.7 в Mihomo). Сам флаг не
|
||||
обновляет старые отпечатки. Исходные ссылки `vless://` не передают эту настройку
|
||||
Mihomo; при прямом импорте нужно постоянное переопределение в клиенте. Для очень
|
||||
старых серверов REALITY, отвергающих ML-KEM, задайте `false` для соответствующего
|
||||
узла в клиенте или обновите сервер. Снятие ограничения версии не исправляет
|
||||
это рукопожатие.
|
||||
|
||||
</Callout>
|
||||
|
||||
|
||||
@@ -105,7 +105,8 @@ vless://<uuid>@<server>:443?security=reality&pbk=<public-key>&sid=<short-id>&sni
|
||||
- **SNI 不匹配。** SNI / server names 必须与目标站点的真实证书匹配,否则握手会暴露伪装。
|
||||
- **私钥泄露。** 永远只把**公钥**分发给客户端。
|
||||
- **流控设置错误。** REALITY + XTLS-Vision 要求在入站的客户端条目和分享链接上都设置 `flow = xtls-rprx-vision`。
|
||||
- **旧客户端内核默认被拒。** **最小客户端版本**留空并不是“不限制”:Xray-core 会退回到所运行内核版本的内置最低值(当前版本为 26.3.27)以保证客户端 TLS 指纹的新鲜度,因此 Mihomo、sing-box 等第三方内核即使配置完全正确也会导致 REALITY 验证失败——表现为客户端超时,只有基于 Xray-core 的应用能连上。只有在必须支持它们时才填 `1.0.0`;这同时也会放行过时的指纹。
|
||||
- **客户端版本限制。** Xray-core v26.9.8+ 在**最小客户端版本**留空时不再设置默认下限,但已明确保存的限制仍生效。较早的内核可能使用内置下限(如 `26.3.27`),导致第三方客户端即使密钥正确也被拒绝。修改前先核对运行中的内核版本;降低限制也会放行较旧的指纹。
|
||||
- **Mihomo 与 ML-KEM。** Xray-core v26.9.8+ 还独立要求 `X25519MLKEM768` key share 位于可选的 `X25519` 之前。Clash/Mihomo YAML 订阅会为 REALITY 节点(含外部链接)启用 `reality-opts.support-x25519mlkem768`,未设置指纹时使用 `chrome`。明确选择的指纹会保留,必须选择支持 ML-KEM 的指纹(Mihomo 使用 uTLS v1.8.7 时可选 `chrome`);开关无法让旧指纹获得新能力。原始 `vless://` 链接不携带这个 Mihomo 配置项,直接导入时仍需持久覆写。对拒绝 ML-KEM 的很旧的 REALITY 服务端,需在客户端按节点将此项覆写为 `false`,或升级服务端。仅清空版本限制无法解决握手问题。
|
||||
|
||||
</Callout>
|
||||
|
||||
|
||||
@@ -1072,8 +1072,11 @@ func (s *SubClashService) applySecurity(proxy map[string]any, security string, s
|
||||
realityOpts["short-id"] = shortID
|
||||
}
|
||||
if len(realityOpts) > 0 {
|
||||
// Xray 26.9.8+ rejects REALITY handshakes without an ML-KEM key share.
|
||||
realityOpts["support-x25519mlkem768"] = true
|
||||
proxy["reality-opts"] = realityOpts
|
||||
}
|
||||
proxy["client-fingerprint"] = "chrome"
|
||||
if fingerprint, ok := realitySettings["fingerprint"].(string); ok && fingerprint != "" {
|
||||
proxy["client-fingerprint"] = fingerprint
|
||||
}
|
||||
|
||||
@@ -161,6 +161,51 @@ func TestBuildProxy_VLESSRealityFieldsForClash(t *testing.T) {
|
||||
if opts["short-id"] != "ab12" {
|
||||
t.Fatalf("short-id = %v, want ab12", opts["short-id"])
|
||||
}
|
||||
if opts["support-x25519mlkem768"] != true {
|
||||
t.Fatalf("ML-KEM support = %v, want true", opts["support-x25519mlkem768"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestClashRealityMLKEMAcrossSources(t *testing.T) {
|
||||
svc := NewSubClashService(false, "", &SubService{})
|
||||
for _, security := range []string{"reality", "tls", "none"} {
|
||||
for _, fingerprint := range []string{"", "chrome", "firefox"} {
|
||||
t.Run(security+"/"+fingerprint, func(t *testing.T) {
|
||||
inbound := &model.Inbound{Listen: "example.com", Port: 443, Protocol: model.VLESS, Settings: `{"encryption":"none"}`}
|
||||
client := model.Client{ID: "11111111-2222-4333-8444-555555555555"}
|
||||
stream := svc.streamData(fmt.Sprintf(`{"network":"tcp","security":%q,"realitySettings":{"serverNames":["example.com"],"shortIds":["ab12"],"settings":{"publicKey":"PBKvalue","fingerprint":%q}}}`, security, fingerprint))
|
||||
link := "vless://" + client.ID + "@example.com:443?type=tcp&security=" + security + "&sni=example.com&pbk=PBKvalue&sid=ab12&fp=" + fingerprint
|
||||
for source, proxy := range map[string]map[string]any{
|
||||
"inbound": svc.buildProxy(svc.SubService, inbound, client, stream, nil),
|
||||
"external": svc.clashProxyFromExternal(link, "external"),
|
||||
} {
|
||||
if proxy == nil {
|
||||
t.Fatalf("%s: missing proxy", source)
|
||||
}
|
||||
opts, exists := proxy["reality-opts"].(map[string]any)
|
||||
if security != "reality" {
|
||||
if exists {
|
||||
t.Fatalf("%s: REALITY options leaked into %s: %#v", source, security, opts)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if opts["support-x25519mlkem768"] != true || opts["public-key"] != "PBKvalue" || opts["short-id"] != "ab12" {
|
||||
t.Fatalf("%s: incorrect REALITY options: %#v", source, opts)
|
||||
}
|
||||
wantFingerprint := fingerprint
|
||||
if wantFingerprint == "" {
|
||||
wantFingerprint = "chrome"
|
||||
}
|
||||
if proxy["client-fingerprint"] != wantFingerprint {
|
||||
t.Fatalf("%s: fingerprint = %v, want %s", source, proxy["client-fingerprint"], wantFingerprint)
|
||||
}
|
||||
if legacyClashProxy(proxy) != nil {
|
||||
t.Fatalf("%s: REALITY must stay excluded from legacy Clash", source)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyTransport_TCPHeader pins the tcp-header validation (clash_service.go ~359):
|
||||
|
||||
Reference in New Issue
Block a user