mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-09 22:55:00 +03:00
fix(xray): keep XDRIVE storage and Vision flow within what xray-core runs
xray-core opens an XDRIVE stream's storage only when the inbound starts listening, so a Google Drive inbound saved with the form's defaults (no folder, no secrets), a local one without a folder, or a template one whose template xray cannot use stopped xray and every other inbound with it. validateXDriveStorage applies the core's storage rules at save, on inbound add/update and on the xray template's inbounds and outbounds: the service must be local, Google Drive or template; local and Google Drive need a remoteFolder; Google Drive needs its three non-empty secrets; a template must pass newTemplateStorage's checks (put, get, list and delete urls, and a list namesRegex with one capture group). A VLESS client kept its xtls-rprx-vision flow when its inbound moved to a transport that cannot carry it, and GetXrayConfig only honoured DisableFlow, so the server demanded Vision there and refused every client. GetXrayConfig and the outbound form now apply the Vision gate, and that gate now follows the core in the backend, the subscription service and the frontend: TLS/REALITY on raw TCP, or VLESS encryption on any transport (xray runs Vision inside encryption.CommonConn whatever the transport, not only on XHTTP). The outbound modal passes the VLESS encryption into the gate and gates Mux on the effective flow.
This commit is contained in:
@@ -1005,6 +1005,78 @@ func validateFinalMaskSalamander(streamSettings string) error {
|
||||
return maskcompat.CheckSalamanderPasswords(stream["finalmask"])
|
||||
}
|
||||
|
||||
// validateXDriveStorage refuses at save the XDRIVE storage xray-core refuses when an inbound starts
|
||||
// listening (stopping every inbound) or an outbound dials.
|
||||
func validateXDriveStorage(streamSettings string) error {
|
||||
var stream struct {
|
||||
Network string `json:"network"`
|
||||
XDriveSettings struct {
|
||||
Service string `json:"service"`
|
||||
RemoteFolder string `json:"remoteFolder"`
|
||||
Secrets []string `json:"secrets"`
|
||||
Template json.RawMessage `json:"template"`
|
||||
} `json:"xdriveSettings"`
|
||||
}
|
||||
if json.Unmarshal([]byte(streamSettings), &stream) != nil || stream.Network != "xdrive" {
|
||||
return nil
|
||||
}
|
||||
xd := stream.XDriveSettings
|
||||
hasFolder := strings.TrimSpace(xd.RemoteFolder) != ""
|
||||
switch xd.Service {
|
||||
case "local":
|
||||
if !hasFolder {
|
||||
return common.NewError(`XDRIVE needs a "remoteFolder": the local folder both ends share.`)
|
||||
}
|
||||
case "Google Drive":
|
||||
if !hasFolder {
|
||||
return common.NewError(`XDRIVE needs a "remoteFolder": the id of the shared Google Drive folder.`)
|
||||
}
|
||||
if len(xd.Secrets) != 3 || slices.ContainsFunc(xd.Secrets, func(s string) bool { return strings.TrimSpace(s) == "" }) {
|
||||
return common.NewError("Google Drive needs 3 secrets, in order: ClientID, ClientSecret, RefreshToken.")
|
||||
}
|
||||
case "template":
|
||||
if len(xd.Template) == 0 || string(xd.Template) == "null" {
|
||||
return common.NewError(`XDRIVE service "template" needs a "template" object describing the storage API.`)
|
||||
}
|
||||
return validateXDriveTemplate(xd.Template)
|
||||
default:
|
||||
return common.NewError(fmt.Sprintf("unsupported XDRIVE service %q: use local, Google Drive or template.", xd.Service))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateXDriveTemplate applies the checks of xray-core's newTemplateStorage
|
||||
// (transport/internet/xdrive/template.go), which only run once the listener starts.
|
||||
func validateXDriveTemplate(raw json.RawMessage) error {
|
||||
type op struct {
|
||||
URL string `json:"url"`
|
||||
NamesRegex string `json:"namesRegex"`
|
||||
}
|
||||
var tmpl struct {
|
||||
Put op `json:"put"`
|
||||
Get op `json:"get"`
|
||||
Delete op `json:"delete"`
|
||||
List op `json:"list"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &tmpl); err != nil {
|
||||
return common.NewError("XDRIVE template is not a valid storage template:", err)
|
||||
}
|
||||
if tmpl.Put.URL == "" || tmpl.Get.URL == "" || tmpl.List.URL == "" || tmpl.Delete.URL == "" {
|
||||
return common.NewError("XDRIVE template needs put, get, list and delete operations, each with a url.")
|
||||
}
|
||||
if tmpl.List.NamesRegex == "" {
|
||||
return common.NewError("XDRIVE template list needs a namesRegex.")
|
||||
}
|
||||
names, err := regexp.Compile(tmpl.List.NamesRegex)
|
||||
if err != nil {
|
||||
return common.NewError("XDRIVE template namesRegex does not compile:", err)
|
||||
}
|
||||
if names.NumSubexp() < 1 {
|
||||
return common.NewError("XDRIVE template namesRegex needs one capture group for the object name.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateFinalMaskJSONSalamander checks a stored whole finalmask: a host's, or the subscription's.
|
||||
func validateFinalMaskJSONSalamander(finalmask string) error {
|
||||
var fm map[string]any
|
||||
@@ -1178,6 +1250,9 @@ func (s *InboundService) AddInbound(inbound *model.Inbound) (*model.Inbound, boo
|
||||
if err := validateFinalMaskSalamander(inbound.StreamSettings); err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
if err := validateXDriveStorage(inbound.StreamSettings); err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
if err := validateShadowsocks2022Inbound(inbound.Protocol, inbound.Settings); err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
@@ -1826,6 +1901,9 @@ func (s *InboundService) UpdateInbound(inbound *model.Inbound) (*model.Inbound,
|
||||
if err := validateFinalMaskSalamander(inbound.StreamSettings); err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
if err := validateXDriveStorage(inbound.StreamSettings); err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
if err := validateShadowsocks2022Inbound(inbound.Protocol, inbound.Settings); err != nil {
|
||||
return inbound, false, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user