- Delete the xray settings page (routes/menu/title), geodata UI/service, XraySettingController/Service and routing-sync write paths; the stored xrayTemplateConfig row loses its last readers (GetXrayConfigTemplate now returns the embedded factory template; B1 already deleted the row). - Settings GeneralTab gains the four trafficStatsSwitches switches (settings.trafficStatsSwitches, B1) with the dual warning copy; entity AllSetting carries the key end to end (TestTrafficStatsSwitchesPlumbing). - Tag-directory consumers (panelOutbound, mtproto egress, node/host outbound pickers) move from POST /xray/ to GET /server/outboundDirectory. - Shared Mux/Sockopt forms and balancer helpers relocated to lib/xray; converter preserves sniffing ips/domains exclusions into the panel sidecar instead of dropping them (B2 gap closed by retention). - Freedom final-rules DB fix seeders retired; getDefaultJsonConfig endpoint removed (no in-repo consumer); locales pruned to referenced keys (pages.xray 331 -> 68) plus the five new settings keys.
94 lines
2.5 KiB
Go
94 lines
2.5 KiB
Go
package service
|
|
|
|
import (
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Helpers relocated from the retired XraySettingService file (ITEM6
|
|
// B4): both are consumed by surviving xray-runtime paths — the
|
|
// outbound-subscription validation filter and the router-config
|
|
// sanitizer in xray.go — not by the deleted template settings page.
|
|
|
|
const (
|
|
unencryptedOutboundProhibitedError = "without TLS or other encryption is prohibited unless the server address is a private IP or domain"
|
|
unencryptedOutboundMinimumVersion = "26.7.11"
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|
|
// isApiRule reports whether a routing rule targets the internal api inbound
|
|
// (inboundTag contains "api" and outboundTag is "api").
|
|
func isApiRule(rule map[string]any) bool {
|
|
if outTag, _ := rule["outboundTag"].(string); outTag != "api" {
|
|
return false
|
|
}
|
|
raw, ok := rule["inboundTag"]
|
|
if !ok {
|
|
return false
|
|
}
|
|
// inboundTag is usually []string but can come as []any from a
|
|
// roundtrip through map[string]any. Accept both shapes.
|
|
switch tags := raw.(type) {
|
|
case []any:
|
|
for _, t := range tags {
|
|
if s, ok := t.(string); ok && s == "api" {
|
|
return true
|
|
}
|
|
}
|
|
case []string:
|
|
if slices.Contains(tags, "api") {
|
|
return true
|
|
}
|
|
case string:
|
|
if tags == "api" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|