mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-24 23:05:01 +03:00
61b129d5b3
Bump xtls/xray-core to 1b1647cd9ddf (v26.10.21) and move the three binary pins (DockerInit.sh, both release.yml fetches) in lockstep. The v26.10.10 release was pulled upstream, so the old pins already 404. XDRIVE (#7182): the core now runs XDRIVE only for VLESS inside VLESS encryption, with no flow, and an outbound only over Mux; it refuses the whole config otherwise. The panel offers XDRIVE only for VLESS, refuses to save an inbound without decryption, never emits a client flow on an XDRIVE stream (config, JSON subscription, forms), and turns Mux on when an outbound picks XDRIVE. An inbound or template outbound stored before the upgrade that the core would refuse is left out of the generated config with a warning, so it no longer stops every other inbound. The service "Google Drive" is now "google-drive" (old name read as a deprecated alias); saves, the schema and both form adapters store the new name. XDRIVE and MASQUE gain a client xmux; both schemas keep it instead of stripping it on an inbound save. Lua scripts (#7124, #7136): the core refuses a routing script beside rules or domainStrategy, and a dns script beside the fallback/parallel switches or a domain-scoped server. The panel always keeps its api routing rule, so a template save now refuses any routing script and a dns script in a refused combination, and a stored one is dropped from the generated config so the core still starts. Noise masks gain a Lua script that the loader compiles inside Build: the in-process build (template save check, hot add) now resolves a relative script the way the xray child does, beside the xray binary, instead of failing it. The mask editor gets a Script field that replaces the noise list. XDNS (#7140): a resolver takes https:// DoH addresses with worker, tlsConfig and quicConfig. The mask editor registered only addrs, so a save dropped the rest; it now edits all four. Finalmask filter (#7206): a new TCP/UDP mask screens an inbound's source IPs. The inbound mask editor offers it with its allow and block lists; it is a no-op on a dialer and an older client core refuses the type, so the share link and JSON subscription drop it like the Go link already does.
200 lines
9.3 KiB
Go
200 lines
9.3 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
)
|
|
|
|
func xdriveInbound(port int, xdriveSettings string) *model.Inbound {
|
|
return &model.Inbound{
|
|
Enable: true, Listen: "127.0.0.1", Port: port, Protocol: model.VLESS,
|
|
Settings: `{"clients":[],"decryption":"` + goldenVlessDecryption + `"}`,
|
|
StreamSettings: `{"network":"xdrive","security":"none","xdriveSettings":` + xdriveSettings + `}`,
|
|
}
|
|
}
|
|
|
|
// xray-core 26.10.17 (#7182) refuses to start an XDRIVE inbound that is not VLESS with
|
|
// "decryption" set, and with it every other inbound of the config.
|
|
func TestSavePathsRefuseXDriveOutsideVlessEncryption(t *testing.T) {
|
|
const local = `{"service":"local","remoteFolder":"/tmp/drive"}`
|
|
vmess := xdriveInbound(18490, local)
|
|
vmess.Protocol = model.VMESS
|
|
vmess.Settings = `{"clients":[]}`
|
|
plain := xdriveInbound(18491, local)
|
|
plain.Settings = `{"clients":[],"decryption":"none"}`
|
|
tests := []struct {
|
|
name string
|
|
save func(t *testing.T) error
|
|
want string
|
|
}{
|
|
{"vmess inbound", func(t *testing.T) error {
|
|
_, _, err := (&InboundService{}).AddInbound(vmess)
|
|
return err
|
|
}, "XDRIVE carries only VLESS, not vmess"},
|
|
{"vless inbound without decryption", func(t *testing.T) error {
|
|
_, _, err := (&InboundService{}).AddInbound(plain)
|
|
return err
|
|
}, "XDRIVE needs VLESS encryption"},
|
|
{"xray template inbound without decryption", func(t *testing.T) error {
|
|
return (&XraySettingService{}).SaveXraySetting(`{"inbounds":[{"tag":"drive","port":18492,"protocol":"vless",
|
|
"settings":` + plain.Settings + `,"streamSettings":` + plain.StreamSettings + `}],
|
|
"outbounds":[{"protocol":"freedom","tag":"direct"}]}`)
|
|
}, "XDRIVE needs VLESS encryption"},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
setupBulkDB(t)
|
|
if err := tc.save(t); err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("save err = %v, want one containing %q", err, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// An XDRIVE inbound saved before xray-core 26.10.17 without VLESS encryption makes the core refuse
|
|
// the whole config at startup; the build leaves that inbound out so the others keep running.
|
|
func TestGetXrayConfig_LeavesOutAStoredXDriveInboundTheCoreRefuses(t *testing.T) {
|
|
seedVlessInbound(t, "plain-drive", 43130, nil)
|
|
if err := database.GetDB().Model(&model.Inbound{}).Where("tag = ?", "plain-drive").
|
|
Update("stream_settings", `{"network":"xdrive","security":"none","xdriveSettings":{"service":"local","remoteFolder":"/tmp/shared"}}`).Error; err != nil {
|
|
t.Fatalf("set stream: %v", err)
|
|
}
|
|
cfg, err := (&XrayService{}).GetXrayConfig()
|
|
if err != nil {
|
|
t.Fatalf("GetXrayConfig: %v", err)
|
|
}
|
|
for _, ib := range cfg.InboundConfigs {
|
|
if ib.Tag == "plain-drive" {
|
|
t.Fatalf("XDRIVE inbound without decryption emitted: %s", ib.Settings)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A template XDRIVE outbound from before 26.10.17 lacks the VLESS encryption and mux the core now
|
|
// requires; the build drops only that one instead of letting the core refuse the whole config.
|
|
func TestGetXrayConfig_DropsATemplateXDriveOutboundTheCoreRefuses(t *testing.T) {
|
|
setupConflictDB(t)
|
|
const stream = `{"network":"xdrive","security":"none","xdriveSettings":{"service":"local","remoteFolder":"/tmp/shared"}}`
|
|
template := `{"outbounds":[{"protocol":"freedom","tag":"direct"},
|
|
{"protocol":"vless","tag":"drive-plain","streamSettings":` + stream + `,
|
|
"settings":{"address":"10.0.0.1","port":443,"id":"11111111-2222-4333-8444-555555555555","encryption":"none"}},
|
|
{"protocol":"vless","tag":"drive-encrypted","streamSettings":` + stream + `,"mux":{"enabled":true,"concurrency":8},
|
|
"settings":{"address":"10.0.0.1","port":443,"id":"11111111-2222-4333-8444-555555555555",
|
|
"encryption":"mlkem768x25519plus.native.0rtt.mMFxPe7lz5xoq2qBk22cQYefu5fpc_2dGR8lMOKem0E"}}]}`
|
|
if err := (&SettingService{}).saveSetting("xrayTemplateConfig", template); err != nil {
|
|
t.Fatalf("seed template: %v", err)
|
|
}
|
|
|
|
cfg, err := (&XrayService{}).GetXrayConfig()
|
|
if err != nil {
|
|
t.Fatalf("GetXrayConfig: %v", err)
|
|
}
|
|
var outbounds []struct {
|
|
Tag string `json:"tag"`
|
|
}
|
|
if err := json.Unmarshal(cfg.OutboundConfigs, &outbounds); err != nil {
|
|
t.Fatalf("decode emitted outbounds: %v", err)
|
|
}
|
|
var tags []string
|
|
for _, ob := range outbounds {
|
|
tags = append(tags, ob.Tag)
|
|
}
|
|
if !slices.Equal(tags, []string{"direct", "drive-encrypted"}) {
|
|
t.Fatalf("emitted outbound tags = %v, want [direct drive-encrypted]", tags)
|
|
}
|
|
}
|
|
|
|
// xray-core 26.10.17 renamed the service to "google-drive" and reads "Google Drive" only as a
|
|
// deprecated alias, so a save posting the old name stores the new one.
|
|
func TestAddInboundStoresTheGoogleDriveServiceUnderItsNewName(t *testing.T) {
|
|
setupBulkDB(t)
|
|
in, _, err := (&InboundService{}).AddInbound(xdriveInbound(18493,
|
|
`{"service":"Google Drive","remoteFolder":"f","secrets":["id","secret","token"]}`))
|
|
if err != nil {
|
|
t.Fatalf("AddInbound: %v", err)
|
|
}
|
|
if !strings.Contains(in.StreamSettings, `"service": "google-drive"`) {
|
|
t.Fatalf("stored streamSettings = %s, want service google-drive", in.StreamSettings)
|
|
}
|
|
}
|
|
|
|
// xray-core opens XDRIVE storage when the listener starts; a storage it refuses there stops every inbound.
|
|
func TestAddInboundRefusesXDriveStorageTheCoreCannotOpen(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
settings string
|
|
want string
|
|
}{
|
|
{"local without folder", `{"service":"local","remoteFolder":""}`, `XDRIVE needs a "remoteFolder"`},
|
|
{"google drive without folder", `{"service":"Google Drive","remoteFolder":" ","secrets":["id","secret","token"]}`, `XDRIVE needs a "remoteFolder"`},
|
|
{"google drive with two secrets", `{"service":"Google Drive","remoteFolder":"f","secrets":["id","secret"]}`, "Google Drive needs 3 secrets"},
|
|
{"google drive with an empty secret", `{"service":"Google Drive","remoteFolder":"f","secrets":["id","","token"]}`, "Google Drive needs 3 secrets"},
|
|
{"template without template", `{"service":"template","remoteFolder":"f"}`, `XDRIVE service "template" needs a "template" object`},
|
|
{"template that is empty", `{"service":"template","remoteFolder":"f","template":{}}`, "XDRIVE template needs put, get, list and delete operations"},
|
|
{"template that is not an object", `{"service":"template","remoteFolder":"f","template":"put"}`, "XDRIVE template is not a valid storage template"},
|
|
{"template without a delete op", `{"service":"template","template":{"put":{"url":"u"},"get":{"url":"u"},"list":{"url":"u","namesRegex":"(.+)"}}}`, "XDRIVE template needs put, get, list and delete operations"},
|
|
{"template without namesRegex", `{"service":"template","template":{"put":{"url":"u"},"get":{"url":"u"},"list":{"url":"u"},"delete":{"url":"u"}}}`, "XDRIVE template list needs a namesRegex"},
|
|
{"template namesRegex without a group", `{"service":"template","template":{"put":{"url":"u"},"get":{"url":"u"},"list":{"url":"u","namesRegex":"name"},"delete":{"url":"u"}}}`, "XDRIVE template namesRegex needs one capture group"},
|
|
{"unknown service", `{"service":"Dropbox","remoteFolder":"f"}`, `unsupported XDRIVE service "Dropbox"`},
|
|
}
|
|
for i, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
setupBulkDB(t)
|
|
_, _, err := (&InboundService{}).AddInbound(xdriveInbound(18460+i, tc.settings))
|
|
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("AddInbound err = %v, want one containing %q", err, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAddInboundAcceptsACompleteXDriveTemplate(t *testing.T) {
|
|
setupBulkDB(t)
|
|
tmpl := `{"service":"template","template":{"put":{"url":"u"},"get":{"url":"u"},"list":{"url":"u","namesRegex":"\"name\":\"([^\"]+)\""},"delete":{"url":"u"}}}`
|
|
if _, _, err := (&InboundService{}).AddInbound(xdriveInbound(18480, tmpl)); err != nil {
|
|
t.Fatalf("AddInbound of a template xray-core can open: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSavePathsRefuseXDriveWithoutStorage(t *testing.T) {
|
|
const want = `XDRIVE needs a "remoteFolder"`
|
|
const empty = `{"service":"local","remoteFolder":""}`
|
|
tests := []struct {
|
|
name string
|
|
save func(t *testing.T) error
|
|
}{
|
|
{"update inbound", func(t *testing.T) error {
|
|
in, _, err := (&InboundService{}).AddInbound(xdriveInbound(18470, `{"service":"local","remoteFolder":"/tmp/drive"}`))
|
|
if err != nil {
|
|
t.Fatalf("AddInbound of a complete XDRIVE inbound: %v", err)
|
|
}
|
|
in.StreamSettings = xdriveInbound(18470, empty).StreamSettings
|
|
_, _, err = (&InboundService{}).UpdateInbound(in)
|
|
return err
|
|
}},
|
|
{"xray template inbound", func(t *testing.T) error {
|
|
return (&XraySettingService{}).SaveXraySetting(`{"inbounds":[{"tag":"drive","port":18471,"protocol":"vless",
|
|
"settings":{"clients":[],"decryption":"none"},"streamSettings":` + xdriveInbound(18471, empty).StreamSettings + `}],
|
|
"outbounds":[{"protocol":"freedom","tag":"direct"}]}`)
|
|
}},
|
|
{"xray template outbound", func(t *testing.T) error {
|
|
return (&XraySettingService{}).SaveXraySetting(`{"outbounds":[{"protocol":"freedom","tag":"direct"},
|
|
{"protocol":"vless","tag":"drive-out","settings":{"address":"drive.example","port":443,"id":"11111111-2222-4333-8444-555555555555","encryption":"none"},
|
|
"streamSettings":` + xdriveInbound(0, empty).StreamSettings + `}]}`)
|
|
}},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
setupBulkDB(t)
|
|
if err := tc.save(t); err == nil || !strings.Contains(err.Error(), want) {
|
|
t.Fatalf("save err = %v, want one containing %q", err, want)
|
|
}
|
|
})
|
|
}
|
|
}
|