| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- package service
- import (
- "encoding/json"
- )
- var routingMatcherKeys = []string{
- "domain", "ip", "port", "sourcePort", "localPort", "network",
- "sourceIP", "localIP", "user", "vlessRoute", "protocol", "attrs", "process",
- }
- func readInboundTags(raw any) []string {
- switch tags := raw.(type) {
- case []string:
- return append([]string(nil), tags...)
- case string:
- if tags == "" {
- return nil
- }
- return []string{tags}
- case []any:
- out := make([]string, 0, len(tags))
- for _, item := range tags {
- if s, ok := item.(string); ok && s != "" {
- out = append(out, s)
- }
- }
- return out
- default:
- return nil
- }
- }
- func writeInboundTags(rule map[string]any, tags []string) {
- if len(tags) == 0 {
- delete(rule, "inboundTag")
- return
- }
- rule["inboundTag"] = tags
- }
- func ruleHasNonInboundMatchers(rule map[string]any) bool {
- for _, key := range routingMatcherKeys {
- if hasRoutingMatcherValue(rule[key]) {
- return true
- }
- }
- return false
- }
- func hasRoutingMatcherValue(raw any) bool {
- switch v := raw.(type) {
- case nil:
- return false
- case string:
- return v != ""
- case float64, int, int64, bool:
- return true
- case []string:
- return len(v) > 0
- case []any:
- return len(v) > 0
- case map[string]any:
- return len(v) > 0
- default:
- return true
- }
- }
- func replaceInboundTagInRules(rules []map[string]any, oldTag, newTag string) bool {
- changed := false
- for _, rule := range rules {
- if replaceInboundTagInRule(rule, oldTag, newTag) {
- changed = true
- }
- }
- return changed
- }
- func replaceInboundTagInRule(rule map[string]any, oldTag, newTag string) bool {
- tags := readInboundTags(rule["inboundTag"])
- if len(tags) == 0 {
- return false
- }
- updated := false
- for i, tag := range tags {
- if tag == oldTag {
- tags[i] = newTag
- updated = true
- }
- }
- if updated {
- writeInboundTags(rule, tags)
- }
- return updated
- }
- func removeInboundTagFromRules(rules []map[string]any, deletedTag string) ([]map[string]any, bool) {
- if deletedTag == "" {
- return rules, false
- }
- changed := false
- out := make([]map[string]any, 0, len(rules))
- for _, rule := range rules {
- tags := readInboundTags(rule["inboundTag"])
- if len(tags) == 0 {
- out = append(out, rule)
- continue
- }
- nextTags := make([]string, 0, len(tags))
- hadDeleted := false
- for _, tag := range tags {
- if tag == deletedTag {
- hadDeleted = true
- continue
- }
- nextTags = append(nextTags, tag)
- }
- if !hadDeleted {
- out = append(out, rule)
- continue
- }
- changed = true
- if len(nextTags) == 0 && !ruleHasNonInboundMatchers(rule) {
- continue
- }
- if len(nextTags) == 0 {
- delete(rule, "inboundTag")
- } else {
- writeInboundTags(rule, nextTags)
- }
- out = append(out, rule)
- }
- return out, changed
- }
- func replaceInboundTagInOutbounds(outbounds []any, oldTag, newTag string) bool {
- changed := false
- for _, outIface := range outbounds {
- out, ok := outIface.(map[string]any)
- if !ok {
- continue
- }
- proto, _ := out["protocol"].(string)
- if proto != "loopback" {
- continue
- }
- settings, ok := out["settings"].(map[string]any)
- if !ok {
- continue
- }
- tag, _ := settings["inboundTag"].(string)
- if tag != oldTag {
- continue
- }
- settings["inboundTag"] = newTag
- changed = true
- }
- return changed
- }
- func removeInboundTagFromOutbounds(outbounds []any, deletedTag string) bool {
- changed := false
- for _, outIface := range outbounds {
- out, ok := outIface.(map[string]any)
- if !ok {
- continue
- }
- proto, _ := out["protocol"].(string)
- if proto != "loopback" {
- continue
- }
- settings, ok := out["settings"].(map[string]any)
- if !ok {
- continue
- }
- tag, _ := settings["inboundTag"].(string)
- if tag != deletedTag {
- continue
- }
- delete(settings, "inboundTag")
- changed = true
- }
- return changed
- }
- func mutateXrayTemplateRouting(raw string, mutate func(cfg map[string]any) bool) (string, bool, error) {
- raw = UnwrapXrayTemplateConfig(raw)
- var cfg map[string]any
- if err := json.Unmarshal([]byte(raw), &cfg); err != nil {
- return raw, false, err
- }
- if !mutate(cfg) {
- return raw, false, nil
- }
- out, err := json.MarshalIndent(cfg, "", " ")
- if err != nil {
- return raw, false, err
- }
- return string(out), true, nil
- }
- func routingRulesFromCfg(cfg map[string]any) []map[string]any {
- routing, _ := cfg["routing"].(map[string]any)
- if routing == nil {
- return nil
- }
- rawRules, ok := routing["rules"].([]any)
- if !ok {
- return nil
- }
- rules := make([]map[string]any, 0, len(rawRules))
- for _, item := range rawRules {
- rule, ok := item.(map[string]any)
- if !ok {
- continue
- }
- rules = append(rules, rule)
- }
- return rules
- }
- func setRoutingRulesInCfg(cfg map[string]any, rules []map[string]any) {
- routing, _ := cfg["routing"].(map[string]any)
- if routing == nil {
- routing = map[string]any{}
- cfg["routing"] = routing
- }
- items := make([]any, len(rules))
- for i, rule := range rules {
- items[i] = rule
- }
- routing["rules"] = items
- }
- func outboundsFromCfg(cfg map[string]any) []any {
- outbounds, _ := cfg["outbounds"].([]any)
- return outbounds
- }
- // PropagateInboundTagRename rewrites routing rules and loopback outbound
- // references when a panel inbound tag changes.
- func (s *XraySettingService) PropagateInboundTagRename(oldTag, newTag string) (bool, error) {
- if oldTag == "" || newTag == "" || oldTag == newTag {
- return false, nil
- }
- template, err := s.GetXrayConfigTemplate()
- if err != nil {
- return false, err
- }
- updated, changed, err := mutateXrayTemplateRouting(template, func(cfg map[string]any) bool {
- mutated := false
- rules := routingRulesFromCfg(cfg)
- if len(rules) > 0 {
- if replaceInboundTagInRules(rules, oldTag, newTag) {
- setRoutingRulesInCfg(cfg, rules)
- mutated = true
- }
- }
- outbounds := outboundsFromCfg(cfg)
- if len(outbounds) > 0 && replaceInboundTagInOutbounds(outbounds, oldTag, newTag) {
- cfg["outbounds"] = outbounds
- mutated = true
- }
- return mutated
- })
- if err != nil || !changed {
- return false, err
- }
- if err := s.SaveXraySetting(updated); err != nil {
- return false, err
- }
- return true, nil
- }
- // RemoveInboundTagReferences drops a deleted inbound tag from routing rules.
- // Rules that only matched that inbound are removed; rules with additional
- // matchers keep the rule and only lose the inboundTag entry.
- func (s *XraySettingService) RemoveInboundTagReferences(deletedTag string) (bool, error) {
- if deletedTag == "" {
- return false, nil
- }
- template, err := s.GetXrayConfigTemplate()
- if err != nil {
- return false, err
- }
- updated, changed, err := mutateXrayTemplateRouting(template, func(cfg map[string]any) bool {
- mutated := false
- rules := routingRulesFromCfg(cfg)
- if len(rules) > 0 {
- nextRules, rulesChanged := removeInboundTagFromRules(rules, deletedTag)
- if rulesChanged {
- setRoutingRulesInCfg(cfg, nextRules)
- mutated = true
- }
- }
- outbounds := outboundsFromCfg(cfg)
- if len(outbounds) > 0 && removeInboundTagFromOutbounds(outbounds, deletedTag) {
- cfg["outbounds"] = outbounds
- mutated = true
- }
- return mutated
- })
- if err != nil || !changed {
- return false, err
- }
- if err := s.SaveXraySetting(updated); err != nil {
- return false, err
- }
- return true, nil
- }
|