fix(sub): enable ML-KEM for Mihomo REALITY subscriptions (#6451)

This commit is contained in:
Lucas
2026-09-11 21:34:57 +08:00
committed by GitHub
parent 9f07951ba7
commit 0a2cd789ba
6 changed files with 103 additions and 23 deletions
+15 -7
View File
@@ -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. - **Leaked private key.** Only ever distribute the **public** key to clients.
- **Wrong flow.** REALITY + XTLS-Vision needs `flow = xtls-rprx-vision` on both - **Wrong flow.** REALITY + XTLS-Vision needs `flow = xtls-rprx-vision` on both
the inbound client entry and the share link. the inbound client entry and the share link.
- **Old client cores rejected by default.** An empty **Min Client Ver** is not - **Client version limits.** Xray-core v26.9.8+ no longer sets a minimum when
"no limit": Xray-core falls back to the built-in minimum of the core build you **Min Client Ver** is empty. An explicitly saved minimum still applies. Earlier
run (26.3.27 in current releases) that keeps client TLS fingerprints fresh, so builds may use a built-in minimum (such as `26.3.27`), rejecting third-party
third-party cores such as Mihomo and sing-box fail REALITY verification even clients even with correct keys. Check the running core version before changing
with a correct config — clients see timeouts while only Xray-core based apps this gate; lowering it also admits older fingerprints.
connect. Set it to `1.0.0` only if you must support them; that also re-admits - **Mihomo and ML-KEM.** Xray-core v26.9.8+ independently requires an
outdated fingerprints. `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> </Callout>
+23 -7
View File
@@ -118,13 +118,29 @@ vless://<uuid>@<server>:443?security=reality&pbk=<public-key>&sid=<short-id>&sni
- **نشت کلید خصوصی.** فقط و فقط **کلید عمومی** را میان کلاینت‌ها توزیع کنید. - **نشت کلید خصوصی.** فقط و فقط **کلید عمومی** را میان کلاینت‌ها توزیع کنید.
- **جریان نادرست.** REALITY + XTLS-Vision به `flow = xtls-rprx-vision` هم در ورودیِ - **جریان نادرست.** REALITY + XTLS-Vision به `flow = xtls-rprx-vision` هم در ورودیِ
مدخل کلاینت و هم در لینک اشتراک‌گذاری نیاز دارد. مدخل کلاینت و هم در لینک اشتراک‌گذاری نیاز دارد.
- **هسته‌های قدیمی کلاینت به‌طور پیش‌فرض رد می‌شوند.** خالی گذاشتن - **محدودیت نسخهٔ کلاینت.** از نسخهٔ
**حداقل نسخه کلاینت** به معنای «بدون محدودیت» نیست: Xray-core به حداقل داخلیِ `Xray-core v26.9.8`
نسخهٔ هسته‌ای که اجرا می‌کنید (در نسخه‌های فعلی 26.3.27) بازمی‌گردد تا اثر انگشت‌های TLS کلاینت‌ها تازه به بعد، خالی بودن **حداقل نسخه کلاینت** حد پایین پیش‌فرض ایجاد نمی‌کند؛ مقدار ذخیره‌شده همچنان اعمال می‌شود.
بمانند؛ در نتیجه هسته‌های شخص ثالث مانند Mihomo و sing-box حتی با پیکربندی نسخه‌های قدیمی‌تر ممکن است حد داخلی مانند
کاملاً درست در تأیید REALITY شکست می‌خورند — کلاینت‌ها تایم‌اوت می‌بینند و فقط `26.3.27`
اپلیکیشن‌های مبتنی بر Xray-core وصل می‌شوند. تنها در صورت نیاز به پشتیبانی از داشته باشند. ابتدا نسخهٔ هستهٔ در حال اجرا را بررسی کنید؛ کاهش حد، اثر انگشت‌های قدیمی را نیز مجاز می‌کند.
آن‌ها مقدار `1.0.0` را تنظیم کنید؛ این کار اثر انگشت‌های قدیمی را هم می‌پذیرد. - **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> </Callout>
+15 -8
View File
@@ -123,14 +123,21 @@ vless://<uuid>@<server>:443?security=reality&pbk=<public-key>&sid=<short-id>&sni
ключ. ключ.
- **Неправильный поток.** Для REALITY + XTLS-Vision нужен `flow = xtls-rprx-vision` - **Неправильный поток.** Для REALITY + XTLS-Vision нужен `flow = xtls-rprx-vision`
как в записи клиента входящего подключения, так и в ссылке для подключения. как в записи клиента входящего подключения, так и в ссылке для подключения.
- **Старые ядра клиентов отклоняются по умолчанию.** Пустое поле - **Ограничения версии клиента.** В Xray-core v26.9.8+ пустое поле
**Мин. версия клиента** не означает «без ограничений»: Xray-core использует **Мин. версия клиента** не задаёт нижнюю границу. Явно сохранённое ограничение
встроенный минимум используемой сборки ядра (26.3.27 в текущих релизах), продолжает действовать. Более ранние сборки могут использовать встроенный
который поддерживает свежесть минимум (например, `26.3.27`) и отклонять сторонние клиенты с правильными ключами.
TLS-отпечатков клиентов, поэтому сторонние ядра, такие как Mihomo и sing-box, Проверьте версию работающего ядра: снижение ограничения допускает старые отпечатки.
не проходят проверку REALITY даже при корректной конфигурации — клиенты видят - **Mihomo и ML-KEM.** Xray-core v26.9.8+ отдельно требует ключ
таймауты, а подключаются только приложения на базе Xray-core. Ставьте `1.0.0`, `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> </Callout>
+2 -1
View File
@@ -105,7 +105,8 @@ vless://<uuid>@<server>:443?security=reality&pbk=<public-key>&sid=<short-id>&sni
- **SNI 不匹配。** SNI / server names 必须与目标站点的真实证书匹配,否则握手会暴露伪装。 - **SNI 不匹配。** SNI / server names 必须与目标站点的真实证书匹配,否则握手会暴露伪装。
- **私钥泄露。** 永远只把**公钥**分发给客户端。 - **私钥泄露。** 永远只把**公钥**分发给客户端。
- **流控设置错误。** REALITY + XTLS-Vision 要求在入站的客户端条目和分享链接上都设置 `flow = xtls-rprx-vision`。 - **流控设置错误。** 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> </Callout>
+3
View File
@@ -1072,8 +1072,11 @@ func (s *SubClashService) applySecurity(proxy map[string]any, security string, s
realityOpts["short-id"] = shortID realityOpts["short-id"] = shortID
} }
if len(realityOpts) > 0 { 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["reality-opts"] = realityOpts
} }
proxy["client-fingerprint"] = "chrome"
if fingerprint, ok := realitySettings["fingerprint"].(string); ok && fingerprint != "" { if fingerprint, ok := realitySettings["fingerprint"].(string); ok && fingerprint != "" {
proxy["client-fingerprint"] = fingerprint proxy["client-fingerprint"] = fingerprint
} }
+45
View File
@@ -161,6 +161,51 @@ func TestBuildProxy_VLESSRealityFieldsForClash(t *testing.T) {
if opts["short-id"] != "ab12" { if opts["short-id"] != "ab12" {
t.Fatalf("short-id = %v, want ab12", opts["short-id"]) 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): // TestApplyTransport_TCPHeader pins the tcp-header validation (clash_service.go ~359):