mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-03 19:55:04 +03:00
19f2f21d81
Xray refuses a VLESS inbound with server decryption whenever fallbacks is non-nil, even an empty list, so an API or imported inbound with decryption and fallbacks: [] took every inbound down. Empty fallback arrays are now normalized away on add, update, config build and node push; non-empty ones are refused with decryption, enabling decryption over stored fallback rules is refused, and a legacy conflicting row holds back the running core instead of stopping it. The form, API docs and locales follow. Closes #7001.
79 lines
2.3 KiB
Go
79 lines
2.3 KiB
Go
package service
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/xtls/xray-core/infra/conf"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
)
|
|
|
|
func vlessDecryptionEnabled(settings string) bool {
|
|
var s struct {
|
|
Decryption string `json:"decryption"`
|
|
}
|
|
return json.Unmarshal([]byte(settings), &s) == nil && vlessEncValueSet(s.Decryption)
|
|
}
|
|
|
|
// normalizeVlessFallbacks also protects API/import callers that bypass the form adapter.
|
|
func normalizeVlessFallbacks(inbound *model.Inbound) error {
|
|
if inbound.Protocol != model.VLESS {
|
|
return nil
|
|
}
|
|
var settings map[string]json.RawMessage
|
|
if err := json.Unmarshal([]byte(inbound.Settings), &settings); err != nil {
|
|
return fmt.Errorf("inbound %d: invalid VLESS settings: %w", inbound.Id, err)
|
|
}
|
|
raw, exists := settings["fallbacks"]
|
|
if !exists || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
|
|
return nil
|
|
}
|
|
var fallbacks []*conf.VLessInboundFallback
|
|
if err := json.Unmarshal(raw, &fallbacks); err != nil {
|
|
return fmt.Errorf("inbound %d: invalid VLESS fallbacks: %w", inbound.Id, err)
|
|
}
|
|
if len(fallbacks) == 0 {
|
|
delete(settings, "fallbacks")
|
|
normalized, err := json.Marshal(settings)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
inbound.Settings = string(normalized)
|
|
return nil
|
|
}
|
|
if vlessDecryptionEnabled(inbound.Settings) {
|
|
return vlessFallbackConflict(inbound.Id)
|
|
}
|
|
for _, fallback := range fallbacks {
|
|
if fallback == nil {
|
|
return fmt.Errorf("inbound %d: VLESS fallback entries must be objects", inbound.Id)
|
|
}
|
|
}
|
|
candidate := conf.VLessInboundConfig{Decryption: "none", Fallbacks: fallbacks}
|
|
if _, err := candidate.Build(); err != nil {
|
|
return fmt.Errorf("inbound %d: invalid VLESS fallbacks: %w", inbound.Id, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func vlessFallbackConflict(id int) error {
|
|
return fmt.Errorf("inbound %d: VLESS fallbacks cannot be used with active server decryption; remove fallback rules before enabling decryption", id)
|
|
}
|
|
|
|
func validateVlessManagedFallbacks(tx *gorm.DB, inbound *model.Inbound) error {
|
|
if inbound.Protocol != model.VLESS || !vlessDecryptionEnabled(inbound.Settings) || inbound.Id == 0 {
|
|
return nil
|
|
}
|
|
fallbacks, err := (&FallbackService{}).BuildFallbacksJSON(tx, inbound.Id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(fallbacks) > 0 {
|
|
return vlessFallbackConflict(inbound.Id)
|
|
}
|
|
return nil
|
|
}
|