mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-24 13:36:24 +00:00
fix: gate embedded unencrypted-outbound rejection on running Xray core version (#6028)
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,8 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
|
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
|
||||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||||
@@ -16,6 +18,11 @@ type XraySettingService struct {
|
|||||||
SettingService
|
SettingService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
unencryptedOutboundProhibitedError = "without TLS or other encryption is prohibited unless the server address is a private IP or domain"
|
||||||
|
unencryptedOutboundMinimumVersion = "26.7.11"
|
||||||
|
)
|
||||||
|
|
||||||
func (s *XraySettingService) SaveXraySetting(newXraySettings string) error {
|
func (s *XraySettingService) SaveXraySetting(newXraySettings string) error {
|
||||||
// The frontend round-trips the whole getXraySetting response back
|
// The frontend round-trips the whole getXraySetting response back
|
||||||
// through the textarea, so if it has ever received a wrapped
|
// through the textarea, so if it has ever received a wrapped
|
||||||
@@ -46,8 +53,15 @@ func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
|
|||||||
if err := json.Unmarshal(xrayConfig.OutboundConfigs, &outbounds); err != nil {
|
if err := json.Unmarshal(xrayConfig.OutboundConfigs, &outbounds); err != nil {
|
||||||
return common.NewError("xray template config invalid: outbounds is not an array:", err)
|
return common.NewError("xray template config invalid: outbounds is not an array:", err)
|
||||||
}
|
}
|
||||||
|
coreVersion := "Unknown"
|
||||||
|
if p != nil {
|
||||||
|
coreVersion = p.GetXrayVersion()
|
||||||
|
}
|
||||||
for _, outbound := range outbounds {
|
for _, outbound := range outbounds {
|
||||||
if err := xray.ValidateOutboundConfig(outbound); err != nil {
|
if err := xray.ValidateOutboundConfig(outbound); err != nil {
|
||||||
|
if shouldSkipLegacyUnencryptedOutboundRejection(coreVersion, err) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
tagged := struct {
|
tagged := struct {
|
||||||
Tag string `json:"tag"`
|
Tag string `json:"tag"`
|
||||||
}{}
|
}{}
|
||||||
@@ -59,6 +73,51 @@ func (s *XraySettingService) CheckXrayConfig(XrayTemplateConfig string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shouldSkipLegacyUnencryptedOutboundRejection lets an older running Xray
|
||||||
|
// core accept an outbound that the newer embedded validator rejects solely
|
||||||
|
// because it is unencrypted and targets a public address. Unknown or malformed
|
||||||
|
// versions preserve the embedded validator's strict behavior.
|
||||||
|
func shouldSkipLegacyUnencryptedOutboundRejection(coreVersion string, err error) bool {
|
||||||
|
if err == nil || !strings.Contains(err.Error(), unencryptedOutboundProhibitedError) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
comparison, ok := compareXrayCoreVersions(coreVersion, unencryptedOutboundMinimumVersion)
|
||||||
|
return ok && comparison < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareXrayCoreVersions(a, b string) (int, bool) {
|
||||||
|
aParts, okA := parseXrayCoreVersionParts(a)
|
||||||
|
bParts, okB := parseXrayCoreVersionParts(b)
|
||||||
|
if !okA || !okB {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
for i := range len(aParts) {
|
||||||
|
if aParts[i] > bParts[i] {
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
if aParts[i] < bParts[i] {
|
||||||
|
return -1, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseXrayCoreVersionParts(version string) ([3]int, bool) {
|
||||||
|
var result [3]int
|
||||||
|
parts := strings.Split(strings.TrimPrefix(strings.TrimSpace(version), "v"), ".")
|
||||||
|
if len(parts) != len(result) {
|
||||||
|
return result, false
|
||||||
|
}
|
||||||
|
for i, part := range parts {
|
||||||
|
n, err := strconv.Atoi(part)
|
||||||
|
if err != nil {
|
||||||
|
return result, false
|
||||||
|
}
|
||||||
|
result[i] = n
|
||||||
|
}
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
|
|
||||||
func (s *XraySettingService) UpdateWarpXraySetting(warpData map[string]string, warpConfig map[string]any) error {
|
func (s *XraySettingService) UpdateWarpXraySetting(warpData map[string]string, warpConfig map[string]any) error {
|
||||||
template, err := s.GetXrayConfigTemplate()
|
template, err := s.GetXrayConfigTemplate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -2,10 +2,58 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestShouldSkipLegacyUnencryptedOutboundRejection(t *testing.T) {
|
||||||
|
prohibited := errors.New("vless without TLS or other encryption is prohibited unless the server address is a private IP or domain")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
version string
|
||||||
|
err error
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{name: "older core", version: "26.4.25", err: prohibited, want: true},
|
||||||
|
{name: "boundary version", version: "26.7.11", err: prohibited, want: false},
|
||||||
|
{name: "newer core", version: "26.10.0", err: prohibited, want: false},
|
||||||
|
{name: "unknown version", version: "Unknown", err: prohibited, want: false},
|
||||||
|
{name: "unparseable version", version: "26.7", err: prohibited, want: false},
|
||||||
|
{name: "empty version", version: "", err: prohibited, want: false},
|
||||||
|
{name: "unrelated validation error", version: "26.4.25", err: errors.New("invalid outbound"), want: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := shouldSkipLegacyUnencryptedOutboundRejection(tt.version, tt.err); got != tt.want {
|
||||||
|
t.Fatalf("shouldSkipLegacyUnencryptedOutboundRejection(%q, %v) = %v, want %v", tt.version, tt.err, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompareXrayCoreVersions(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
a, b string
|
||||||
|
want int
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{a: "26.7.11", b: "26.7.11", want: 0, ok: true},
|
||||||
|
{a: "26.10.0", b: "26.7.11", want: 1, ok: true},
|
||||||
|
{a: "v26.4.25", b: "26.7.11", want: -1, ok: true},
|
||||||
|
{a: "Unknown", b: "26.7.11", want: 0, ok: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
got, ok := compareXrayCoreVersions(tt.a, tt.b)
|
||||||
|
if got != tt.want || ok != tt.ok {
|
||||||
|
t.Errorf("compareXrayCoreVersions(%q, %q) = (%d, %v), want (%d, %v)", tt.a, tt.b, got, ok, tt.want, tt.ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnwrapXrayTemplateConfig(t *testing.T) {
|
func TestUnwrapXrayTemplateConfig(t *testing.T) {
|
||||||
real := `{"log":{},"inbounds":[],"outbounds":[],"routing":{}}`
|
real := `{"log":{},"inbounds":[],"outbounds":[],"routing":{}}`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user