2023-02-09 19:18:06 +00:00
package service
import (
"encoding/json"
"errors"
2025-08-07 18:35:11 +00:00
"runtime"
2023-02-09 19:18:06 +00:00
"sync"
2025-09-18 13:10:08 +00:00
"strconv"
2024-03-10 21:31:24 +00:00
2023-02-09 19:18:06 +00:00
"x-ui/logger"
"x-ui/xray"
2025-09-18 13:10:08 +00:00
json_util "x-ui/util/json_util"
2023-02-18 12:37:32 +00:00
2023-02-09 19:18:06 +00:00
"go.uber.org/atomic"
)
2024-03-10 21:31:24 +00:00
var (
p * xray . Process
lock sync . Mutex
2025-08-07 18:35:11 +00:00
isNeedXrayRestart atomic . Bool // Indicates that restart was requested for Xray
isManuallyStopped atomic . Bool // Indicates that Xray was stopped manually from the panel
2024-03-10 21:31:24 +00:00
result string
)
2023-02-09 19:18:06 +00:00
type XrayService struct {
inboundService InboundService
settingService SettingService
2023-06-04 21:02:19 +00:00
xrayAPI xray . XrayAPI
2023-02-09 19:18:06 +00:00
}
func ( s * XrayService ) IsXrayRunning ( ) bool {
return p != nil && p . IsRunning ( )
}
2025-09-18 13:10:08 +00:00
func ( s * XrayService ) GetApiPort ( ) int {
if p == nil {
return 0
}
return p . GetAPIPort ( )
}
2023-02-09 19:18:06 +00:00
func ( s * XrayService ) GetXrayErr ( ) error {
if p == nil {
return nil
}
2025-08-07 18:35:11 +00:00
err := p . GetErr ( )
if runtime . GOOS == "windows" && err . Error ( ) == "exit status 1" {
// exit status 1 on Windows means that Xray process was killed
// as we kill process to stop in on Windows, this is not an error
return nil
}
return err
2023-02-09 19:18:06 +00:00
}
func ( s * XrayService ) GetXrayResult ( ) string {
if result != "" {
return result
}
if s . IsXrayRunning ( ) {
return ""
}
if p == nil {
return ""
}
2025-08-07 18:35:11 +00:00
2023-02-09 19:18:06 +00:00
result = p . GetResult ( )
2025-08-07 18:35:11 +00:00
if runtime . GOOS == "windows" && result == "exit status 1" {
// exit status 1 on Windows means that Xray process was killed
// as we kill process to stop in on Windows, this is not an error
return ""
}
2023-02-09 19:18:06 +00:00
return result
}
func ( s * XrayService ) GetXrayVersion ( ) string {
if p == nil {
return "Unknown"
}
return p . GetVersion ( )
}
2023-02-18 12:37:32 +00:00
2025-03-12 19:13:51 +00:00
func RemoveIndex ( s [ ] any , index int ) [ ] any {
2023-02-09 19:18:06 +00:00
return append ( s [ : index ] , s [ index + 1 : ] ... )
}
func ( s * XrayService ) GetXrayConfig ( ) ( * xray . Config , error ) {
templateConfig , err := s . settingService . GetXrayConfigTemplate ( )
if err != nil {
return nil , err
}
xrayConfig := & xray . Config { }
err = json . Unmarshal ( [ ] byte ( templateConfig ) , xrayConfig )
if err != nil {
return nil , err
}
2025-09-18 13:10:08 +00:00
2023-02-09 19:18:06 +00:00
inbounds , err := s . inboundService . GetAllInbounds ( )
if err != nil {
return nil , err
}
2025-09-18 13:10:08 +00:00
uniqueSpeeds := make ( map [ int ] bool )
for _ , inbound := range inbounds {
if ! inbound . Enable {
continue
}
dbClients , _ := s . inboundService . GetClients ( inbound )
for _ , dbClient := range dbClients {
if dbClient . SpeedLimit > 0 {
uniqueSpeeds [ dbClient . SpeedLimit ] = true
}
}
}
var finalPolicy map [ string ] interface { }
if xrayConfig . Policy != nil {
if err := json . Unmarshal ( xrayConfig . Policy , & finalPolicy ) ; err != nil {
logger . Warningf ( "Failed to parse the policy in the template: %v" , err )
finalPolicy = make ( map [ string ] interface { } )
}
} else {
finalPolicy = make ( map [ string ] interface { } )
}
var policyLevels map [ string ] interface { }
if levels , ok := finalPolicy [ "levels" ] . ( map [ string ] interface { } ) ; ok {
policyLevels = levels
} else {
policyLevels = make ( map [ string ] interface { } )
}
// 3. [Important modification]: Ensure the integrity of the level 0 policy, which is key to enabling device restrictions and default user statistics.
var level0 map [ string ] interface { }
if l0 , ok := policyLevels [ "0" ] . ( map [ string ] interface { } ) ; ok {
// If level 0 already exists in the template, use it as the base for modifications.
level0 = l0
} else {
// If it does not exist in the template, create a brand new map.
level0 = make ( map [ string ] interface { } )
}
// [Chinese comment]: Regardless of whether level 0 exists, supplement or override the following key parameters for it.
// handshake and connIdle are prerequisites to activate Xray connection statistics,
// uplinkOnly and downlinkOnly set to 0 mean no speed limit, which is the default behavior for level 0 users.
// statsUserUplink and statsUserDownlink ensure that user traffic can be counted.
level0 [ "handshake" ] = 4
level0 [ "connIdle" ] = 300
level0 [ "uplinkOnly" ] = 0
level0 [ "downlinkOnly" ] = 0
level0 [ "statsUserUplink" ] = true
level0 [ "statsUserDownlink" ] = true
// [Added]: Add this key option to enable Xray-core's online IP statistics feature.
// This is a prerequisite for the proper functioning of the "device restriction" feature.
level0 [ "statsUserOnline" ] = true
// Write the fully configured level 0 back to policyLevels to ensure the final generated config.json is correct.
policyLevels [ "0" ] = level0
// 4. Iterate through all collected speed limits and create a corresponding level for each unique speed limit
for speed := range uniqueSpeeds {
// Create a level for each speed, where the level's name is the string representation of the speed
// For example, the speed 1024 KB/s corresponds to the level "1024"
policyLevels [ strconv . Itoa ( speed ) ] = map [ string ] interface { } {
"downlinkOnly" : speed ,
"uplinkOnly" : speed ,
"handshake" : 4 ,
"connIdle" : 300 ,
"statsUserUplink" : true ,
"statsUserDownlink" : true ,
"statsUserOnline" : true ,
}
}
// 5. Write the modified levels back to the policy object, serialize it back to xrayConfig.Policy, and apply the generated policy to the Xray configuration
finalPolicy [ "levels" ] = policyLevels
policyJSON , err := json . Marshal ( finalPolicy )
if err != nil {
return nil , err
}
xrayConfig . Policy = json_util . RawMessage ( policyJSON )
// =================================================================
// Add logs here to print the final generated speed limit policy
// =================================================================
if len ( uniqueSpeeds ) > 0 {
finalPolicyLog , _ := json . Marshal ( policyLevels )
logger . Infof ( "已为Xray动态生成〔 限速策略〕 : %s" , string ( finalPolicyLog ) )
}
s . inboundService . AddTraffic ( nil , nil )
2023-02-09 19:18:06 +00:00
for _ , inbound := range inbounds {
if ! inbound . Enable {
continue
}
2025-09-18 13:10:08 +00:00
inboundConfig := inbound . GenXrayInboundConfig ( )
speedByEmail := make ( map [ string ] int )
speedById := make ( map [ string ] int )
dbClients , _ := s . inboundService . GetClients ( inbound )
for _ , dbc := range dbClients {
if dbc . Email != "" {
speedByEmail [ dbc . Email ] = dbc . SpeedLimit
}
if dbc . ID != "" {
speedById [ dbc . ID ] = dbc . SpeedLimit
}
}
var settings map [ string ] interface { }
if err := json . Unmarshal ( [ ] byte ( inbound . Settings ) , & settings ) ; err != nil {
logger . Warningf ( "Failed to parse inbound.Settings (inbound %d): %v, skipping this inbound" , inbound . Id , err )
continue
}
originalClients , ok := settings [ "clients" ] . ( [ ] interface { } )
if ok {
clientStats := inbound . ClientStats
var xrayClients [ ] interface { }
for _ , clientRaw := range originalClients {
c , ok := clientRaw . ( map [ string ] interface { } )
if ! ok {
continue
}
if en , ok := c [ "enable" ] . ( bool ) ; ok && ! en {
if em , _ := c [ "email" ] . ( string ) ; em != "" {
logger . Infof ( "User marked as disabled in settings removed from Xray config: %s" , em )
}
continue
}
email , _ := c [ "email" ] . ( string )
idStr , _ := c [ "id" ] . ( string )
disabledByStat := false
for _ , stat := range clientStats {
if stat . Email == email && ! stat . Enable {
disabledByStat = true
break
}
}
if disabledByStat {
logger . Infof ( "User disabled and removed from Xray config: %s" , email )
continue
}
xrayClient := make ( map [ string ] interface { } )
if id , ok := c [ "id" ] ; ok { xrayClient [ "id" ] = id }
if email != "" { xrayClient [ "email" ] = email }
if flow , ok := c [ "flow" ] ; ok {
if fs , ok2 := flow . ( string ) ; ok2 && fs == "xtls-rprx-vision-udp443" {
xrayClient [ "flow" ] = "xtls-rprx-vision"
} else {
xrayClient [ "flow" ] = flow
}
}
if password , ok := c [ "password" ] ; ok { xrayClient [ "password" ] = password }
if method , ok := c [ "method" ] ; ok { xrayClient [ "method" ] = method }
level := 0
if email != "" {
if v , ok := speedByEmail [ email ] ; ok && v > 0 {
level = v
}
}
if level == 0 && idStr != "" {
if v , ok := speedById [ idStr ] ; ok && v > 0 {
level = v
}
}
if level == 0 {
if sl , ok := c [ "speedLimit" ] ; ok {
switch vv := sl . ( type ) {
case float64 :
level = int ( vv )
case int :
level = vv
case int64 :
level = int ( vv )
case string :
if n , err := strconv . Atoi ( vv ) ; err == nil {
level = n
}
}
}
}
if level > 0 && email != "" {
logger . Infof ( "Applied independent speed limit for user %s: %d KB/s" , email , level )
}
// =================================================================
xrayClient [ "level" ] = level
xrayClients = append ( xrayClients , xrayClient )
}
settings [ "clients" ] = xrayClients
finalSettingsForXray , err := json . Marshal ( settings )
if err != nil {
logger . Warningf ( "Failed to serialize inbound settings for Xray in GetXrayConfig for inbound %d: %v, skipping this inbound" , inbound . Id , err )
continue
}
inboundConfig . Settings = json_util . RawMessage ( finalSettingsForXray )
}
2023-02-09 19:18:06 +00:00
// get settings clients
2025-03-12 19:13:51 +00:00
settings := map [ string ] any { }
2023-02-09 19:18:06 +00:00
json . Unmarshal ( [ ] byte ( inbound . Settings ) , & settings )
2025-03-12 19:13:51 +00:00
clients , ok := settings [ "clients" ] . ( [ ] any )
2023-02-09 19:18:06 +00:00
if ok {
// check users active or not
clientStats := inbound . ClientStats
for _ , clientTraffic := range clientStats {
2023-04-09 19:43:18 +00:00
indexDecrease := 0
2023-02-09 19:18:06 +00:00
for index , client := range clients {
2025-03-12 19:13:51 +00:00
c := client . ( map [ string ] any )
2023-02-09 19:18:06 +00:00
if c [ "email" ] == clientTraffic . Email {
2023-02-18 12:37:32 +00:00
if ! clientTraffic . Enable {
2023-04-09 19:43:18 +00:00
clients = RemoveIndex ( clients , index - indexDecrease )
indexDecrease ++
2024-07-08 21:08:00 +00:00
logger . Infof ( "Remove Inbound User %s due to expiration or traffic limit" , c [ "email" ] )
2023-02-09 19:18:06 +00:00
}
}
}
}
2023-04-09 19:43:18 +00:00
// clear client config for additional parameters
2025-03-12 19:13:51 +00:00
var final_clients [ ] any
2023-04-09 19:43:18 +00:00
for _ , client := range clients {
2025-03-12 19:13:51 +00:00
c := client . ( map [ string ] any )
2023-04-09 19:43:18 +00:00
if c [ "enable" ] != nil {
if enable , ok := c [ "enable" ] . ( bool ) ; ok && ! enable {
continue
}
}
for key := range c {
2023-07-27 08:28:12 +00:00
if key != "email" && key != "id" && key != "password" && key != "flow" && key != "method" {
2023-04-09 19:43:18 +00:00
delete ( c , key )
}
2023-04-25 18:13:29 +00:00
if c [ "flow" ] == "xtls-rprx-vision-udp443" {
c [ "flow" ] = "xtls-rprx-vision"
}
2023-04-09 19:43:18 +00:00
}
2025-03-12 19:13:51 +00:00
final_clients = append ( final_clients , any ( c ) )
2023-04-09 19:43:18 +00:00
}
settings [ "clients" ] = final_clients
2023-04-24 10:43:25 +00:00
modifiedSettings , err := json . MarshalIndent ( settings , "" , " " )
2023-02-09 19:18:06 +00:00
if err != nil {
return nil , err
}
2023-02-18 12:37:32 +00:00
2023-02-09 19:18:06 +00:00
inbound . Settings = string ( modifiedSettings )
}
2023-12-08 16:21:43 +00:00
2023-12-08 17:45:21 +00:00
if len ( inbound . StreamSettings ) > 0 {
// Unmarshal stream JSON
2025-03-12 19:13:51 +00:00
var stream map [ string ] any
2023-12-08 17:45:21 +00:00
json . Unmarshal ( [ ] byte ( inbound . StreamSettings ) , & stream )
// Remove the "settings" field under "tlsSettings" and "realitySettings"
2025-03-12 19:13:51 +00:00
tlsSettings , ok1 := stream [ "tlsSettings" ] . ( map [ string ] any )
realitySettings , ok2 := stream [ "realitySettings" ] . ( map [ string ] any )
2023-12-08 17:45:21 +00:00
if ok1 || ok2 {
if ok1 {
delete ( tlsSettings , "settings" )
} else if ok2 {
delete ( realitySettings , "settings" )
}
2023-12-08 16:21:43 +00:00
}
2023-12-08 17:45:21 +00:00
delete ( stream , "externalProxy" )
2023-12-08 16:21:43 +00:00
newStream , err := json . MarshalIndent ( stream , "" , " " )
if err != nil {
return nil , err
}
inbound . StreamSettings = string ( newStream )
}
2025-09-18 13:10:08 +00:00
2023-02-09 19:18:06 +00:00
xrayConfig . InboundConfigs = append ( xrayConfig . InboundConfigs , * inboundConfig )
}
return xrayConfig , nil
}
func ( s * XrayService ) GetXrayTraffic ( ) ( [ ] * xray . Traffic , [ ] * xray . ClientTraffic , error ) {
if ! s . IsXrayRunning ( ) {
2024-07-08 21:08:00 +00:00
err := errors . New ( "xray is not running" )
logger . Debug ( "Attempted to fetch Xray traffic, but Xray is not running:" , err )
return nil , nil , err
2023-02-09 19:18:06 +00:00
}
2024-07-08 21:08:00 +00:00
apiPort := p . GetAPIPort ( )
s . xrayAPI . Init ( apiPort )
2023-06-04 21:02:19 +00:00
defer s . xrayAPI . Close ( )
2024-07-08 21:08:00 +00:00
traffic , clientTraffic , err := s . xrayAPI . GetTraffic ( true )
if err != nil {
logger . Debug ( "Failed to fetch Xray traffic:" , err )
return nil , nil , err
}
return traffic , clientTraffic , nil
2023-02-09 19:18:06 +00:00
}
func ( s * XrayService ) RestartXray ( isForce bool ) error {
lock . Lock ( )
defer lock . Unlock ( )
2025-08-07 18:35:11 +00:00
logger . Debug ( "restart Xray, force:" , isForce )
isManuallyStopped . Store ( false )
2023-02-09 19:18:06 +00:00
xrayConfig , err := s . GetXrayConfig ( )
if err != nil {
return err
}
2024-01-01 15:07:56 +00:00
if s . IsXrayRunning ( ) {
2025-08-07 18:35:11 +00:00
if ! isForce && p . GetConfig ( ) . Equals ( xrayConfig ) && ! isNeedXrayRestart . Load ( ) {
logger . Debug ( "It does not need to restart Xray" )
2023-02-09 19:18:06 +00:00
return nil
}
p . Stop ( )
}
p = xray . NewProcess ( xrayConfig )
result = ""
2023-06-04 21:02:19 +00:00
err = p . Start ( )
if err != nil {
return err
}
2025-08-07 18:35:11 +00:00
2023-06-04 21:02:19 +00:00
return nil
2023-02-09 19:18:06 +00:00
}
func ( s * XrayService ) StopXray ( ) error {
lock . Lock ( )
defer lock . Unlock ( )
2025-08-07 18:35:11 +00:00
isManuallyStopped . Store ( true )
2024-07-08 21:08:00 +00:00
logger . Debug ( "Attempting to stop Xray..." )
2023-02-09 19:18:06 +00:00
if s . IsXrayRunning ( ) {
return p . Stop ( )
}
return errors . New ( "xray is not running" )
}
func ( s * XrayService ) SetToNeedRestart ( ) {
isNeedXrayRestart . Store ( true )
}
func ( s * XrayService ) IsNeedRestartAndSetFalse ( ) bool {
2023-03-17 16:07:49 +00:00
return isNeedXrayRestart . CompareAndSwap ( true , false )
2023-02-09 19:18:06 +00:00
}
2025-08-07 18:35:11 +00:00
// Check if Xray is not running and wasn't stopped manually, i.e. crashed
func ( s * XrayService ) DidXrayCrash ( ) bool {
return ! s . IsXrayRunning ( ) && ! isManuallyStopped . Load ( )
2025-09-18 13:10:08 +00:00
}