1
0

json_dns_test.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. package sub
  2. import (
  3. "strings"
  4. "testing"
  5. "github.com/mhsanaei/3x-ui/v3/internal/database"
  6. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  7. "github.com/mhsanaei/3x-ui/v3/internal/xray"
  8. )
  9. const dnsTestStream = `{"network":"tcp","security":"tls","tlsSettings":{"serverName":"base.sni"}}`
  10. func docDnsBlock(t *testing.T, doc map[string]any) map[string]any {
  11. t.Helper()
  12. dns, _ := doc["dns"].(map[string]any)
  13. if dns == nil {
  14. t.Fatalf("doc has no dns block: %v", doc["dns"])
  15. }
  16. return dns
  17. }
  18. func onlySubJsonDoc(t *testing.T, js *SubJsonService, subId string) map[string]any {
  19. t.Helper()
  20. out, _, err := js.GetJson(subId, "req.example.com", true)
  21. if err != nil {
  22. t.Fatalf("GetJson: %v", err)
  23. }
  24. docs := parseSubJsonDocs(t, out)
  25. if len(docs) != 1 {
  26. t.Fatalf("docs = %d, want 1:\n%s", len(docs), out)
  27. }
  28. return docs[0]
  29. }
  30. // A bare servers array must replace the template resolver, not append to it.
  31. func TestSubJsonDns_ArrayReplacesTemplateServers(t *testing.T) {
  32. seedSubDB(t)
  33. seedSubInbound(t, "s1", "tcpin", 4901, 1, dnsTestStream)
  34. js := NewSubJsonService("", "", "", "", NewSubService(""))
  35. js.SetDnsConfig(`["https://dns.google/dns-query", {"address": "tls://1.1.1.1", "domains": ["geosite:youtube"]}]`)
  36. dns := docDnsBlock(t, onlySubJsonDoc(t, js, "s1"))
  37. servers, _ := dns["servers"].([]any)
  38. if len(servers) != 2 {
  39. t.Fatalf("servers = %v, want 2", servers)
  40. }
  41. if servers[0] != "https://dns.google/dns-query" {
  42. t.Fatalf("servers[0] = %v", servers[0])
  43. }
  44. second, _ := servers[1].(map[string]any)
  45. if second["address"] != "tls://1.1.1.1" {
  46. t.Fatalf("servers[1] = %v", second)
  47. }
  48. if domains, _ := second["domains"].([]any); strings.Join(stringify(domains), ",") != "geosite:youtube" {
  49. t.Fatalf("servers[1].domains = %v", second["domains"])
  50. }
  51. if _, hasTemplate := dns["tag"]; hasTemplate {
  52. t.Fatalf("template dns keys leaked into the override: %v", dns)
  53. }
  54. }
  55. func TestSubJsonDns_ObjectReplacesWholeBlock(t *testing.T) {
  56. seedSubDB(t)
  57. seedSubInbound(t, "s1", "tcpin", 4902, 1, dnsTestStream)
  58. js := NewSubJsonService("", "", "", "", NewSubService(""))
  59. js.SetDnsConfig(`{"tag":"panel_dns","queryStrategy":"UseIPv4","disableCache":true,"hosts":{"example.com":"1.2.3.4"},"servers":[{"address":"1.1.1.1","skipFallback":true}]}`)
  60. dns := docDnsBlock(t, onlySubJsonDoc(t, js, "s1"))
  61. if dns["tag"] != "panel_dns" || dns["queryStrategy"] != "UseIPv4" || dns["disableCache"] != true {
  62. t.Fatalf("dns header = %v", dns)
  63. }
  64. hosts, _ := dns["hosts"].(map[string]any)
  65. if hosts["example.com"] != "1.2.3.4" {
  66. t.Fatalf("dns hosts = %v", dns["hosts"])
  67. }
  68. servers, _ := dns["servers"].([]any)
  69. if len(servers) != 1 {
  70. t.Fatalf("servers = %v", servers)
  71. }
  72. server, _ := servers[0].(map[string]any)
  73. if server["address"] != "1.1.1.1" || server["skipFallback"] != true {
  74. t.Fatalf("server = %v", server)
  75. }
  76. }
  77. // The explicit panel DNS block wins over the profile's, while the profile keeps
  78. // owning the routing rules.
  79. func TestSubJsonDns_OverridesRoutingProfileDns(t *testing.T) {
  80. seedSubDB(t)
  81. seedSubInbound(t, "s1", "tcpin", 4903, 1, dnsTestStream)
  82. js := NewSubJsonService("", "", "", bakedRoutingPayload, NewSubService(""))
  83. js.SetDnsConfig(`["9.9.9.9"]`)
  84. doc := onlySubJsonDoc(t, js, "s1")
  85. dns := docDnsBlock(t, doc)
  86. servers, _ := dns["servers"].([]any)
  87. if len(servers) != 1 || servers[0] != "9.9.9.9" {
  88. t.Fatalf("servers = %v, want the panel override only", servers)
  89. }
  90. if hosts, _ := dns["hosts"].(map[string]any); len(hosts) != 0 {
  91. t.Fatalf("profile dns hosts survived the override: %v", hosts)
  92. }
  93. want := "domain->block,domain->proxy,domain->direct,ip->direct,network->proxy"
  94. if got := strings.Join(ruleSignatures(t, doc), ","); got != want {
  95. t.Fatalf("rules = %v\nwant %v", got, want)
  96. }
  97. }
  98. func TestSubJsonDns_InvalidSettingKeepsTemplateDns(t *testing.T) {
  99. cases := []struct {
  100. name string
  101. value string
  102. }{
  103. {"malformed JSON", `{"servers": [`},
  104. {"bare string", `"8.8.8.8"`},
  105. {"servers not an array", `{"servers": "8.8.8.8"}`},
  106. {"entry without address", `[{"domains": ["geosite:youtube"]}]`},
  107. {"non-string entry", `[53]`},
  108. }
  109. for _, tc := range cases {
  110. t.Run(tc.name, func(t *testing.T) {
  111. seedSubDB(t)
  112. seedSubInbound(t, "s1", "tcpin", 4904, 1, dnsTestStream)
  113. js := NewSubJsonService("", "", "", "", NewSubService(""))
  114. js.SetDnsConfig(tc.value)
  115. dns := docDnsBlock(t, onlySubJsonDoc(t, js, "s1"))
  116. if dns["tag"] != "dns_out" || dns["queryStrategy"] != "UseIP" {
  117. t.Fatalf("template dns header = %v", dns)
  118. }
  119. servers, _ := dns["servers"].([]any)
  120. if len(servers) != 1 {
  121. t.Fatalf("template servers = %v", servers)
  122. }
  123. first, _ := servers[0].(map[string]any)
  124. if first["address"] != "8.8.8.8" {
  125. t.Fatalf("template server = %v", first)
  126. }
  127. })
  128. }
  129. }
  130. func TestSubJsonDns_BlankKeepsTemplateDns(t *testing.T) {
  131. seedSubDB(t)
  132. seedSubInbound(t, "s1", "tcpin", 4905, 1, dnsTestStream)
  133. js := NewSubJsonService("", "", "", "", NewSubService(""))
  134. js.SetDnsConfig(" ")
  135. dns := docDnsBlock(t, onlySubJsonDoc(t, js, "s1"))
  136. servers, _ := dns["servers"].([]any)
  137. first, _ := servers[0].(map[string]any)
  138. if first["address"] != "8.8.8.8" {
  139. t.Fatalf("template server = %v", first)
  140. }
  141. }
  142. // Balancer documents are built from the same template, so they carry the
  143. // override too.
  144. func TestSubJsonDns_AppliesToBalancerDocuments(t *testing.T) {
  145. seedSubDB(t)
  146. tcp := seedSubInbound(t, "s1", "tcpin", 4906, 1, dnsTestStream)
  147. seedSubBalancer(t, &model.SubBalancer{
  148. Remark: "auto", Strategy: "random", InboundIds: []int{tcp.Id}, SortOrder: 1, Enabled: true,
  149. })
  150. js := NewSubJsonService("", "", "", "", NewSubService(""))
  151. js.SetDnsConfig(`["https://dns.google/dns-query"]`)
  152. out, _, err := js.GetJson("s1", "req.example.com", true)
  153. if err != nil {
  154. t.Fatalf("GetJson: %v", err)
  155. }
  156. balancerDoc := findDocByRemarks(parseSubJsonDocs(t, out), "auto")
  157. if balancerDoc == nil {
  158. t.Fatalf("balancer doc missing:\n%s", out)
  159. }
  160. servers, _ := docDnsBlock(t, balancerDoc)["servers"].([]any)
  161. if len(servers) != 1 || servers[0] != "https://dns.google/dns-query" {
  162. t.Fatalf("balancer dns servers = %v", servers)
  163. }
  164. }
  165. // The validator rejects a block whose types xray cannot decode, even when the
  166. // servers list itself looks fine.
  167. func TestSubJsonDns_BrokenBlockKeepsTemplateDns(t *testing.T) {
  168. seedSubDB(t)
  169. seedSubInbound(t, "s1", "tcpin", 4907, 1, dnsTestStream)
  170. js := NewSubJsonService("", "", "", "", NewSubService(""))
  171. js.SetDnsConfig(`{"servers": ["1.1.1.1"], "hosts": 5}`)
  172. dns := docDnsBlock(t, onlySubJsonDoc(t, js, "s1"))
  173. servers, _ := dns["servers"].([]any)
  174. first, _ := servers[0].(map[string]any)
  175. if len(servers) != 1 || first["address"] != "8.8.8.8" {
  176. t.Fatalf("template dns = %v", dns)
  177. }
  178. }
  179. // An unusable routing profile degrades to an empty spec; the DNS override must
  180. // still reach the document.
  181. func TestSubJsonDns_AppliesWhenRoutingProfileUnusable(t *testing.T) {
  182. seedSubDB(t)
  183. seedSubInbound(t, "s1", "tcpin", 4908, 1, dnsTestStream)
  184. js := NewSubJsonService("", "", "", "not-a-routing-payload", NewSubService(""))
  185. js.SetDnsConfig(`["9.9.9.9"]`)
  186. doc := onlySubJsonDoc(t, js, "s1")
  187. servers, _ := docDnsBlock(t, doc)["servers"].([]any)
  188. if len(servers) != 1 || servers[0] != "9.9.9.9" {
  189. t.Fatalf("dns servers = %v", servers)
  190. }
  191. want := "network->proxy"
  192. if got := strings.Join(ruleSignatures(t, doc), ","); got != want {
  193. t.Fatalf("rules = %v, want the plain template rule %v", got, want)
  194. }
  195. }
  196. // The dummy info node is emitted as a document too, so it carries the panel DNS.
  197. func TestSubJsonDns_AppliesToInfoNodeDocument(t *testing.T) {
  198. setupInfoNodeTestDB(t)
  199. db := database.GetDB()
  200. ib := &model.Inbound{
  201. Id: 1, UserId: 1, Remark: "Germany-VLESS", Enable: true, Port: 443,
  202. Protocol: model.VLESS,
  203. Settings: `{"clients":[{"id":"c1-uuid","email":"[email protected]","subId":"sub-json","enable":true,"totalGB":10737418240}]}`,
  204. StreamSettings: `{"network":"tcp","security":"none"}`,
  205. }
  206. if err := db.Create(ib).Error; err != nil {
  207. t.Fatal(err)
  208. }
  209. if err := db.Create(&model.ClientRecord{Id: 1, Email: "[email protected]", SubID: "sub-json", UUID: "c1-uuid", Enable: true, TotalGB: 10737418240}).Error; err != nil {
  210. t.Fatal(err)
  211. }
  212. if err := db.Create(&model.ClientInbound{InboundId: 1, ClientId: 1}).Error; err != nil {
  213. t.Fatal(err)
  214. }
  215. if err := db.Create(&xray.ClientTraffic{InboundId: 1, Email: "[email protected]", Up: 1073741824, Down: 1073741824, Total: 10737418240, Enable: true}).Error; err != nil {
  216. t.Fatal(err)
  217. }
  218. sub := NewSubService("{{EMAIL}}|📊{{TRAFFIC_LEFT}}")
  219. sub.subInfoNodeEnable = true
  220. js := NewSubJsonService("", "", "", "", sub)
  221. js.SetDnsConfig(`["https://dns.google/dns-query"]`)
  222. out, _, err := js.GetJson("sub-json", "sub.example.com", true)
  223. if err != nil {
  224. t.Fatalf("GetJson: %v", err)
  225. }
  226. docs := parseSubJsonDocs(t, out)
  227. if len(docs) != 2 {
  228. t.Fatalf("docs = %d, want info node + inbound:\n%s", len(docs), out)
  229. }
  230. servers, _ := docDnsBlock(t, docs[0])["servers"].([]any)
  231. if len(servers) != 1 || servers[0] != "https://dns.google/dns-query" {
  232. t.Fatalf("info node dns servers = %v", servers)
  233. }
  234. }