udphop.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. package maskcompat
  2. import (
  3. "maps"
  4. "slices"
  5. "strings"
  6. )
  7. // UpgradeLegacyUDPHop moves the quicParams.udpHop xray-core 26.9.9 ignores into an intervalRemote
  8. // "udphop" mask, rewriting only finalmask's own keys: a merged finalmask shares quicParams and udp.
  9. func UpgradeLegacyUDPHop(finalmask any) bool {
  10. fm, _ := finalmask.(map[string]any)
  11. quicParams, _ := fm["quicParams"].(map[string]any)
  12. legacy, found := quicParams["udpHop"]
  13. if !found {
  14. return false
  15. }
  16. restQuic := maps.Clone(quicParams)
  17. delete(restQuic, "udpHop")
  18. if len(restQuic) == 0 {
  19. delete(fm, "quicParams")
  20. } else {
  21. fm["quicParams"] = restQuic
  22. }
  23. hop, _ := legacy.(map[string]any)
  24. ports := hop["ports"]
  25. if isEmptyHopValue(ports) || hasUDPHopMask(fm) {
  26. return true
  27. }
  28. settings := map[string]any{"mode": "intervalRemote", "remotePorts": ports}
  29. if interval := hop["interval"]; !isEmptyHopValue(interval) {
  30. settings["interval"] = interval
  31. }
  32. masks, _ := fm["udp"].([]any)
  33. fm["udp"] = append(slices.Clip(masks), map[string]any{"type": "udphop", "settings": settings})
  34. return true
  35. }
  36. // NormalizeUDPHop keeps the last udphop mask alone at the end of udp: the core reverses the list
  37. // and refuses ("incorrect index") a mask that dials its own socket anywhere but the socket end.
  38. func NormalizeUDPHop(finalmask any) bool {
  39. fm, _ := finalmask.(map[string]any)
  40. masks, _ := fm["udp"].([]any)
  41. last, hops := -1, 0
  42. for i, mask := range masks {
  43. if isUDPHopMask(mask) {
  44. last, hops = i, hops+1
  45. }
  46. }
  47. if last < 0 || (hops == 1 && last == len(masks)-1) {
  48. return false
  49. }
  50. kept := make([]any, 0, len(masks)) // hops >= 1, so the re-appended hop still fits
  51. for _, mask := range masks {
  52. if !isUDPHopMask(mask) {
  53. kept = append(kept, mask)
  54. }
  55. }
  56. fm["udp"] = append(kept, masks[last])
  57. return true
  58. }
  59. // WithoutUDPHop returns finalmask minus its udphop masks, for a default that yields to a hop
  60. // range already set; finalmask itself is left untouched.
  61. func WithoutUDPHop(finalmask map[string]any) map[string]any {
  62. masks, _ := finalmask["udp"].([]any)
  63. if !slices.ContainsFunc(masks, isUDPHopMask) {
  64. return finalmask
  65. }
  66. out := maps.Clone(finalmask)
  67. out["udp"] = slices.DeleteFunc(slices.Clone(masks), isUDPHopMask)
  68. return out
  69. }
  70. // HasUDPHopMask reports whether finalmask already dials through a udphop mask.
  71. func HasUDPHopMask(finalmask any) bool {
  72. fm, _ := finalmask.(map[string]any)
  73. return hasUDPHopMask(fm)
  74. }
  75. func hasUDPHopMask(fm map[string]any) bool {
  76. masks, _ := fm["udp"].([]any)
  77. return slices.ContainsFunc(masks, isUDPHopMask)
  78. }
  79. func isUDPHopMask(entry any) bool {
  80. mask, _ := entry.(map[string]any)
  81. maskType, _ := mask["type"].(string)
  82. return strings.EqualFold(maskType, "udphop")
  83. }
  84. func isEmptyHopValue(value any) bool {
  85. switch v := value.(type) {
  86. case nil:
  87. return true
  88. case string:
  89. return strings.TrimSpace(v) == ""
  90. case float64:
  91. return v == 0
  92. }
  93. return false
  94. }