1
0

dnsconf.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. // Package dnsconf validates the JSON-subscription DNS setting against xray's own
  2. // schema, so a block the client could not load never reaches an emitted document.
  3. package dnsconf
  4. import (
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "strings"
  9. "github.com/xtls/xray-core/infra/conf"
  10. )
  11. // Parse resolves the setting into the dns subtree to emit: a full dns object, or
  12. // a bare array of servers wrapped into one. A blank value means "no override".
  13. func Parse(raw string) (map[string]any, error) {
  14. trimmed := strings.TrimSpace(raw)
  15. if trimmed == "" {
  16. return nil, nil
  17. }
  18. var decoded any
  19. if err := json.Unmarshal([]byte(trimmed), &decoded); err != nil {
  20. return nil, fmt.Errorf("invalid DNS JSON: %w", err)
  21. }
  22. block := make(map[string]any)
  23. servers, isList := decoded.([]any)
  24. if isList {
  25. block["servers"] = servers
  26. } else {
  27. object, isObject := decoded.(map[string]any)
  28. if !isObject {
  29. return nil, errors.New("DNS config must be a JSON object or an array of servers")
  30. }
  31. block = object
  32. }
  33. if err := validate(block); err != nil {
  34. return nil, err
  35. }
  36. return block, nil
  37. }
  38. // validate decodes the block into xray's own schema. Build() is deliberately not
  39. // run: it resolves geosite tokens from geodata files the panel may not have.
  40. func validate(block map[string]any) (err error) {
  41. // Third-party parser fed by a panel setting: a panic must degrade to
  42. // "unusable value", never take the panel or sub server down.
  43. defer func() {
  44. if panicValue := recover(); panicValue != nil {
  45. err = fmt.Errorf("invalid DNS config: %v", panicValue)
  46. }
  47. }()
  48. payload, err := json.Marshal(block)
  49. if err != nil {
  50. return fmt.Errorf("invalid DNS config: %w", err)
  51. }
  52. var parsed conf.DNSConfig
  53. if err := json.Unmarshal(payload, &parsed); err != nil {
  54. return fmt.Errorf("invalid DNS config: %w", err)
  55. }
  56. servers, _ := block["servers"].([]any)
  57. if len(servers) == 0 {
  58. // xray quietly installs the system resolver when no server is
  59. // configured, which would leak lookups outside the tunnel.
  60. return errors.New(`"servers" must list at least one DNS server`)
  61. }
  62. for index, entry := range servers {
  63. if err := validateServer(index, entry); err != nil {
  64. return err
  65. }
  66. }
  67. if err := validateClientIP("dns", parsed.ClientIP); err != nil {
  68. return err
  69. }
  70. for index, server := range parsed.Servers {
  71. if err := validateClientIP(fmt.Sprintf("DNS server #%d", index+1), server.ClientIP); err != nil {
  72. return err
  73. }
  74. }
  75. return nil
  76. }
  77. // validateServer enforces the address rule Build() would: xray refuses a name
  78. // server without one, but only reports it when the client starts.
  79. func validateServer(index int, entry any) error {
  80. label := fmt.Sprintf("DNS server #%d", index+1)
  81. switch server := entry.(type) {
  82. case string:
  83. if strings.TrimSpace(server) == "" {
  84. return fmt.Errorf("%s is empty", label)
  85. }
  86. case map[string]any:
  87. address, ok := server["address"]
  88. if !ok {
  89. return fmt.Errorf(`%s needs a non-empty "address"`, label)
  90. }
  91. text, ok := address.(string)
  92. if ok && strings.TrimSpace(text) == "" {
  93. return fmt.Errorf(`%s needs a non-empty "address"`, label)
  94. }
  95. if _, isObject := address.(map[string]any); !ok && !isObject {
  96. return fmt.Errorf(`%s needs a non-empty "address"`, label)
  97. }
  98. default:
  99. return fmt.Errorf("%s must be a string or an object", label)
  100. }
  101. return nil
  102. }
  103. func validateClientIP(label string, clientIP *conf.Address) error {
  104. if clientIP != nil && !clientIP.Family().IsIP() {
  105. return fmt.Errorf("%s clientIp must be an IP address", label)
  106. }
  107. return nil
  108. }