mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-04 04:05:04 +03:00
fix(inbounds): reject incompatible VLESS fallbacks and prevent Xray crashes (#7003)
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.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user