xray_balancer_override.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package service
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "strings"
  7. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  8. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  9. )
  10. var errOverrideTargetUnknown = errors.New("override target is not an outbound in the running config")
  11. // balancerOverrides keeps the panel's overrides across routing reloads, which drop
  12. // them. The package lock guards it, as it does every change to the running core.
  13. var balancerOverrides balancerOverrideSet
  14. // balancerOverrideSet belongs to one core process: a restart clears overrides,
  15. // so records made for an earlier process are never re-applied.
  16. type balancerOverrideSet struct {
  17. process *xray.Process
  18. targets map[string]string
  19. }
  20. // set forces balancer tag to target in the running core and records it; an
  21. // empty target clears the override.
  22. func (s *balancerOverrideSet) set(process *xray.Process, api *xray.XrayAPI, tag, target string) error {
  23. coreTarget := ""
  24. if target != "" {
  25. resolved, err := resolveOverrideTarget(process.GetConfig(), target)
  26. if err != nil {
  27. return err
  28. }
  29. coreTarget = resolved
  30. }
  31. if err := api.SetBalancerTarget(tag, coreTarget); err != nil {
  32. return err
  33. }
  34. if s.process != process {
  35. s.process = process
  36. s.targets = map[string]string{}
  37. }
  38. if target == "" {
  39. delete(s.targets, tag)
  40. } else {
  41. s.targets[tag] = target
  42. }
  43. return nil
  44. }
  45. // reapply restores the recorded overrides once cfg is live, dropping any whose
  46. // balancer or target cfg no longer has.
  47. func (s *balancerOverrideSet) reapply(process *xray.Process, api *xray.XrayAPI, cfg *xray.Config) {
  48. if s.process != process {
  49. return
  50. }
  51. routing := map[string]any{}
  52. if len(cfg.RouterConfig) > 0 {
  53. _ = json.Unmarshal(cfg.RouterConfig, &routing)
  54. }
  55. for tag, target := range s.targets {
  56. if !routingTagIsBalancer(routing, tag) {
  57. delete(s.targets, tag)
  58. continue
  59. }
  60. coreTarget, err := resolveOverrideTarget(cfg, target)
  61. if err != nil {
  62. logger.Warning("balancer [", tag, "] override dropped:", err)
  63. delete(s.targets, tag)
  64. }
  65. if err := api.SetBalancerTarget(tag, coreTarget); err != nil {
  66. logger.Warning("re-apply override of balancer [", tag, "] failed:", err)
  67. }
  68. }
  69. }
  70. // resolveOverrideTarget maps target to the outbound tag the core routes to: a
  71. // balancer becomes the _bl_ loopback outbound whose rule feeds it.
  72. func resolveOverrideTarget(cfg *xray.Config, target string) (string, error) {
  73. routing := map[string]any{}
  74. if len(cfg.RouterConfig) > 0 {
  75. if err := json.Unmarshal(cfg.RouterConfig, &routing); err != nil {
  76. return "", err
  77. }
  78. }
  79. if loopback := balancerLoopbackTag(routing, target); loopback != "" && outboundTagExists(cfg.OutboundConfigs, loopback) {
  80. return loopback, nil
  81. }
  82. if outboundTagExists(cfg.OutboundConfigs, target) {
  83. return target, nil
  84. }
  85. return "", fmt.Errorf("%w: %q", errOverrideTargetUnknown, target)
  86. }
  87. func balancerLoopbackTag(routing map[string]any, balancerTag string) string {
  88. rules, _ := routing["rules"].([]any)
  89. for _, r := range rules {
  90. rule, ok := r.(map[string]any)
  91. if !ok || rule["balancerTag"] != balancerTag {
  92. continue
  93. }
  94. inboundTags, _ := rule["inboundTag"].([]any)
  95. if len(inboundTags) == 0 {
  96. continue
  97. }
  98. if tag, ok := inboundTags[0].(string); ok && strings.HasPrefix(tag, "_bl_") {
  99. return tag
  100. }
  101. }
  102. return ""
  103. }