feat(sub): make the host "Mihomo X25519" switch drop ML-KEM (#6803)

The per-host Mihomo X25519 switch has been stored since managed hosts landed,
but no renderer read it, while every REALITY proxy was forced to
support-x25519mlkem768: true. On some networks DPI drops the second segment of
an ML-KEM ClientHello and blackholes the server for that client. A host with the
switch on now gets support-x25519mlkem768: false and no ML-KEM hint in its vless
link; switches saved while they had no effect are cleared once, so upgrading
does not silently change existing subscriptions.

A REALITY host's fingerprint now reaches the Clash and JSON outputs, and every
host endpoint gets its own deep copy of the stream, so one host's blank SNI,
fingerprint, Host header or path no longer inherits another host's value.
This commit is contained in:
Aleksey
2026-10-24 07:01:48 +03:00
committed by GitHub
parent a403faee23
commit 41bafb3fd5
24 changed files with 370 additions and 26 deletions
+42
View File
@@ -459,3 +459,45 @@ func TestMigrateMtprotoCustomShareAddrWithUnusableSameAddressHost(t *testing.T)
})
}
}
// Mihomo X25519 was stored but ignored before it started dropping ML-KEM; a row
// switched on back then must not change its subscription output on upgrade.
func TestMigrate_PreexistingMihomoX25519IsResetOnce(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "x-ui.db")
if err := InitDB(dbPath); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = CloseDB() })
ib := seedInboundWithStream(t, "mx-legacy", 5559, `{"network":"tcp","security":"reality"}`)
host := &model.Host{InboundId: ib.Id, Remark: "legacy", Address: "cdn.example.com", MihomoX25519: true}
if err := GetDB().Create(host).Error; err != nil {
t.Fatalf("seed host: %v", err)
}
if err := GetDB().Where("seeder_name = ?", "HostMihomoX25519Reset").Delete(&model.HistoryOfSeeders{}).Error; err != nil {
t.Fatalf("clear seeder history: %v", err)
}
reopen := func() model.Host {
t.Helper()
if err := CloseDB(); err != nil {
t.Fatalf("CloseDB: %v", err)
}
if err := InitDB(dbPath); err != nil {
t.Fatalf("reopen InitDB: %v", err)
}
var got model.Host
if err := GetDB().First(&got, host.Id).Error; err != nil {
t.Fatalf("load host: %v", err)
}
return got
}
if got := reopen(); got.MihomoX25519 {
t.Fatalf("pre-existing mihomoX25519 = true after upgrade, want it reset to false")
}
if err := GetDB().Model(&model.Host{}).Where("id = ?", host.Id).Update("mihomo_x25519", true).Error; err != nil {
t.Fatalf("switch on after upgrade: %v", err)
}
if got := reopen(); !got.MihomoX25519 {
t.Fatalf("mihomoX25519 set after the upgrade was reset again on restart")
}
}